From 22e1bf48423f9267e3150e9f9422fffabfa9daab Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Mon, 12 Jun 2023 09:53:55 +0200 Subject: [PATCH] Added easy access to search input on mobile pages --- src/components/Containers/Paginator.tsx | 2 +- src/components/Contents/FolderPath.tsx | 60 +++++++++++++++++++ .../PanelComponents/ReturnButton.tsx | 21 ++----- src/components/PostPage.tsx | 12 ++-- src/components/ThumbnailHeader.tsx | 15 ++++- ...ollTopOnChange.ts => useScrollOnChange.ts} | 12 ++++ src/pages/archives/videos/c/[uid].tsx | 48 ++++++++------- src/pages/archives/videos/index.tsx | 42 ++++++------- src/pages/archives/videos/v/[uid].tsx | 23 ++++--- src/pages/chronicles/[slug]/index.tsx | 27 +++++---- src/pages/contents/[slug].tsx | 8 +-- src/pages/contents/all.tsx | 34 ++++++----- src/pages/contents/folder/[slug].tsx | 42 ++----------- src/pages/library/[slug]/index.tsx | 23 +++---- src/pages/library/index.tsx | 35 ++++++----- src/pages/news/index.tsx | 33 +++++----- src/pages/wiki/[slug]/index.tsx | 50 +++++++--------- src/pages/wiki/chronology.tsx | 12 ++-- src/pages/wiki/index.tsx | 35 ++++++----- src/pages/wiki/weapons/[slug].tsx | 24 ++++---- src/pages/wiki/weapons/index.tsx | 54 +++++++++-------- src/types/ids.ts | 1 + 22 files changed, 326 insertions(+), 287 deletions(-) create mode 100644 src/components/Contents/FolderPath.tsx rename src/hooks/{useScrollTopOnChange.ts => useScrollOnChange.ts} (55%) diff --git a/src/components/Containers/Paginator.tsx b/src/components/Containers/Paginator.tsx index 88ebb93..11013be 100644 --- a/src/components/Containers/Paginator.tsx +++ b/src/components/Containers/Paginator.tsx @@ -5,7 +5,7 @@ import { atoms } from "contexts/atoms"; import { isUndefined } from "helpers/asserts"; import { useAtomGetter } from "helpers/atoms"; import { useFormat } from "hooks/useFormat"; -import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; +import { useScrollTopOnChange } from "hooks/useScrollOnChange"; import { Ids } from "types/ids"; /* diff --git a/src/components/Contents/FolderPath.tsx b/src/components/Contents/FolderPath.tsx new file mode 100644 index 0000000..663f73b --- /dev/null +++ b/src/components/Contents/FolderPath.tsx @@ -0,0 +1,60 @@ +import { useRef } from "react"; +import { Button, TranslatedButton } from "components/Inputs/Button"; +import { atoms } from "contexts/atoms"; +import { ParentFolderPreviewFragment } from "graphql/generated"; +import { useAtomSetter } from "helpers/atoms"; +import { useScrollRightOnChange } from "hooks/useScrollOnChange"; +import { Ids } from "types/ids"; +import { filterHasAttributes } from "helpers/asserts"; +import { prettySlug } from "helpers/formatters"; +import { Ico } from "components/Ico"; + +interface Props { + path: ParentFolderPreviewFragment[]; +} + +export const FolderPath = ({ path }: Props): JSX.Element => { + useScrollRightOnChange(Ids.ContentsFolderPath, [path]); + const setMenuGesturesEnabled = useAtomSetter(atoms.layout.menuGesturesEnabled); + const gestureReenableTimeout = useRef(); + + return ( +
+
{ + if (gestureReenableTimeout.current) clearTimeout(gestureReenableTimeout.current); + setMenuGesturesEnabled(false); + }} + onPointerLeave={() => { + gestureReenableTimeout.current = setTimeout(() => setMenuGesturesEnabled(true), 500); + }} + className={`-mx-4 flex place-items-center justify-start gap-x-1 gap-y-4 + overflow-x-auto px-4 pb-10 scrollbar-none`}> + {path.map((pathFolder, index) => ( + <> + {pathFolder.slug === "root" ? ( +
+
+ ); +}; diff --git a/src/components/PanelComponents/ReturnButton.tsx b/src/components/PanelComponents/ReturnButton.tsx index 0a80ca3..c99e9a2 100644 --- a/src/components/PanelComponents/ReturnButton.tsx +++ b/src/components/PanelComponents/ReturnButton.tsx @@ -2,10 +2,8 @@ import { useCallback } from "react"; import { Button } from "components/Inputs/Button"; import { TranslatedProps } from "types/TranslatedProps"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { isUndefined } from "helpers/asserts"; -import { atoms } from "contexts/atoms"; -import { useAtomGetter } from "helpers/atoms"; import { useFormat } from "hooks/useFormat"; +import { cJoin } from "helpers/className"; /* * ╭─────────────╮ @@ -15,27 +13,18 @@ import { useFormat } from "hooks/useFormat"; interface Props { href: string; title: string | null | undefined; - - displayOnlyOn?: "1ColumnLayout" | "3ColumnsLayout"; className?: string; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -export const ReturnButton = ({ href, title, displayOnlyOn, className }: Props): JSX.Element => { +export const ReturnButton = ({ href, title, className }: Props): JSX.Element => { const { format } = useFormat(); - const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout); return ( - <> - {((is3ColumnsLayout && displayOnlyOn === "3ColumnsLayout") || - (!is3ColumnsLayout && displayOnlyOn === "1ColumnLayout") || - isUndefined(displayOnlyOn)) && ( -
-
- )} - +
+
); }; diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx index 02e8f74..bb22707 100644 --- a/src/components/PostPage.tsx +++ b/src/components/PostPage.tsx @@ -1,4 +1,4 @@ -import { Fragment, useCallback } from "react"; +import { useCallback } from "react"; import { AppLayout, AppLayoutRequired } from "./AppLayout"; import { getTocFromMarkdawn, Markdawn, TableOfContents } from "./Markdown/Markdawn"; import { ReturnButton } from "./PanelComponents/ReturnButton"; @@ -91,13 +91,8 @@ export const PostPage = ({ const contentPanel = ( - {returnHref && returnTitle && ( - + {is1ColumnLayout && returnHref && returnTitle && ( + )} {displayThumbnailHeader ? ( @@ -109,6 +104,7 @@ export const PostPage = ({ categories={filterHasAttributes(post.categories?.data, ["attributes"]).map((category) => formatCategory(category.attributes.slug) )} + releaseDate={post.date} languageSwitcher={ languageSwitcherProps.locales.size > 1 ? ( diff --git a/src/components/ThumbnailHeader.tsx b/src/components/ThumbnailHeader.tsx index 9e3b403..a693b67 100644 --- a/src/components/ThumbnailHeader.tsx +++ b/src/components/ThumbnailHeader.tsx @@ -2,7 +2,7 @@ import { Chip } from "components/Chip"; import { Img } from "components/Img"; import { InsetBox } from "components/Containers/InsetBox"; import { Markdawn } from "components/Markdown/Markdawn"; -import { UploadImageFragment } from "graphql/generated"; +import { DatePickerFragment, UploadImageFragment } from "graphql/generated"; import { prettyInlineTitle, slugify } from "helpers/formatters"; import { ImageQuality } from "helpers/img"; import { useAtomGetter } from "helpers/atoms"; @@ -21,6 +21,7 @@ interface Props { description?: string | null | undefined; type?: string; categories?: string[]; + releaseDate?: DatePickerFragment; thumbnail?: UploadImageFragment | null | undefined; className?: string; languageSwitcher?: JSX.Element; @@ -37,9 +38,10 @@ export const ThumbnailHeader = ({ categories, description, languageSwitcher, + releaseDate, className, }: Props): JSX.Element => { - const { format } = useFormat(); + const { format, formatDate } = useFormat(); const { showLightBox } = useAtomGetter(atoms.lightBox); return ( @@ -76,6 +78,15 @@ export const ThumbnailHeader = ({ )} + {releaseDate && ( +
+

{format("release_date")}

+
+ +
+
+ )} + {categories && categories.length > 0 && (

{format("category", { count: categories.length })}

diff --git a/src/hooks/useScrollTopOnChange.ts b/src/hooks/useScrollOnChange.ts similarity index 55% rename from src/hooks/useScrollTopOnChange.ts rename to src/hooks/useScrollOnChange.ts index bc35052..36405cf 100644 --- a/src/hooks/useScrollTopOnChange.ts +++ b/src/hooks/useScrollOnChange.ts @@ -14,3 +14,15 @@ export const useScrollTopOnChange = (id: Ids, deps: DependencyList, enabled = tr // eslint-disable-next-line react-hooks/exhaustive-deps }, [id, ...deps, enabled]); }; + +// Scroll to top of element "id" when "deps" update. +export const useScrollRightOnChange = (id: Ids, deps: DependencyList, enabled = true): void => { + useEffect(() => { + if (enabled) { + logger.log("Change detected. Scrolling to right"); + const elem = document.querySelector(`#${CSS.escape(id)}`); + elem?.scrollTo({ left: elem.scrollWidth, behavior: "smooth" }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id, ...deps, enabled]); +}; diff --git a/src/pages/archives/videos/c/[uid].tsx b/src/pages/archives/videos/c/[uid].tsx index 206b207..ca36703 100644 --- a/src/pages/archives/videos/c/[uid].tsx +++ b/src/pages/archives/videos/c/[uid].tsx @@ -27,6 +27,8 @@ import { getReadySdk } from "graphql/sdk"; import { Paginator } from "components/Containers/Paginator"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; +import { useAtomGetter } from "helpers/atoms"; +import { atoms } from "contexts/atoms"; /* * ╭─────────────╮ @@ -63,6 +65,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => { const { format } = useFormat(); const hoverable = useDeviceSupportsHover(); const router = useTypedRouter(queryParamSchema); + const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout); const sortingMethods = useMemo( () => [ @@ -147,14 +150,27 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); + const searchInput = ( + { + setPage(1); + setQuery(newQuery); + if (isDefinedAndNotEmpty(newQuery)) { + sendAnalytics("Videos/Channel", "Change search term"); + } else { + sendAnalytics("Videos/Channel", "Clear search term"); + } + }} + /> + ); + const subPanel = ( - + {!is1ColumnLayout && ( + + )} { - { - setPage(1); - setQuery(newQuery); - if (isDefinedAndNotEmpty(newQuery)) { - sendAnalytics("Videos", "Change search term"); - } else { - sendAnalytics("Videos", "Clear search term"); - } - }} - /> + {!is1ColumnLayout &&
{searchInput}
} { const contentPanel = ( + {is1ColumnLayout &&
{searchInput}
}
- - + {!is1ColumnLayout && ( + <> + + + + )} @@ -56,12 +56,9 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => { const contentPanel = ( - + {is1ColumnLayout && ( + + )}
diff --git a/src/pages/chronicles/[slug]/index.tsx b/src/pages/chronicles/[slug]/index.tsx index 48e9c14..9671185 100644 --- a/src/pages/chronicles/[slug]/index.tsx +++ b/src/pages/chronicles/[slug]/index.tsx @@ -16,12 +16,14 @@ import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { getOpenGraph } from "helpers/openGraph"; import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; import { getDescription } from "helpers/description"; -import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; +import { useScrollTopOnChange } from "hooks/useScrollOnChange"; import { Ids } from "types/ids"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; import { ElementsSeparator } from "helpers/component"; import { ChroniclesLists } from "components/Chronicles/ChroniclesLists"; +import { useAtomGetter } from "helpers/atoms"; +import { atoms } from "contexts/atoms"; /* * ╭────────╮ @@ -35,6 +37,7 @@ interface Props extends AppLayoutRequired { const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element => { const { format, formatContentType, formatCategory } = useFormat(); + const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout); useScrollTopOnChange(Ids.ContentPanel, [chronicle.slug]); const [selectedTranslation, LanguageSwitcher, languageSwitcherProps] = useSmartLanguage({ @@ -67,24 +70,22 @@ const Chronicle = ({ chronicle, chapters, ...otherProps }: Props): JSX.Element = const subPanel = ( - - + {!is1ColumnLayout && ( + <> + + + + )} + ); const contentPanel = ( - + {is1ColumnLayout && ( + + )} {isDefined(selectedTranslation) ? ( <> diff --git a/src/pages/contents/[slug].tsx b/src/pages/contents/[slug].tsx index 15057df..5392a0c 100644 --- a/src/pages/contents/[slug].tsx +++ b/src/pages/contents/[slug].tsx @@ -14,7 +14,7 @@ import { prettyInlineTitle, prettySlug } from "helpers/formatters"; import { isUntangibleGroupItem } from "helpers/libraryItem"; import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts"; import { ContentWithTranslations } from "types/types"; -import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; +import { useScrollTopOnChange } from "hooks/useScrollOnChange"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { getOpenGraph } from "helpers/openGraph"; import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales"; @@ -226,11 +226,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => { const contentPanel = ( - + {is1ColumnLayout && }
{ const { format, formatCategory, formatContentType, formatLanguage } = useFormat(); const router = useTypedRouter(queryParamSchema); const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened); + const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout); const sortingMethods = useMemo( () => [ @@ -152,6 +153,22 @@ const Contents = (props: Props): JSX.Element => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); + const searchInput = ( + { + setPage(1); + setQuery(name); + if (isDefinedAndNotEmpty(name)) { + sendAnalytics("Contents/All", "Change search term"); + } else { + sendAnalytics("Contents/All", "Clear search term"); + } + }} + /> + ); + const subPanel = ( { - { - setPage(1); - setQuery(name); - if (isDefinedAndNotEmpty(name)) { - sendAnalytics("Contents/All", "Change search term"); - } else { - sendAnalytics("Contents/All", "Clear search term"); - } - }} - /> + {!is1ColumnLayout &&
{searchInput}
} + { const contentPanel = ( + {is1ColumnLayout &&
{searchInput}
}
{ + setPage(1); + setQuery(name); + if (isDefinedAndNotEmpty(name)) { + sendAnalytics("News", "Change search term"); + } else { + sendAnalytics("News", "Clear search term"); + } + }} + /> + ); + const subPanel = ( { - { - setPage(1); - setQuery(name); - if (isDefinedAndNotEmpty(name)) { - sendAnalytics("News", "Change search term"); - } else { - sendAnalytics("News", "Clear search term"); - } - }} - /> + {!is1ColumnLayout &&
{searchInput}
} { const contentPanel = ( + {is1ColumnLayout &&
{searchInput}
}
+ ); + const subPanel = ( {[ - is3ColumnsLayout && ( - - ), + is3ColumnsLayout && searchInput, {intersectionIds.map((id, index) => ( @@ -126,12 +128,8 @@ const WeaponPage = ({ weapon, primaryName, aliases, ...otherProps }: Props): JSX const contentPanel = ( - + {!is3ColumnsLayout &&
{searchInput}
} + { const { format, formatCategory, formatWeaponType, formatLanguage } = useFormat(); const hoverable = useDeviceSupportsHover(); const router = useTypedRouter(queryParamSchema); + const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout); const languageOptions = useMemo(() => { const memo = @@ -132,36 +135,35 @@ const Weapons = (props: Props): JSX.Element => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); + const searchInput = ( + { + setPage(1); + setQuery(name); + if (isDefinedAndNotEmpty(name)) { + sendAnalytics("Weapons", "Change search term"); + } else { + sendAnalytics("Weapons", "Clear search term"); + } + }} + /> + ); + const subPanel = ( - + {!is1ColumnLayout && } + - { - setPage(1); - setQuery(name); - if (isDefinedAndNotEmpty(name)) { - sendAnalytics("Weapons", "Change search term"); - } else { - sendAnalytics("Weapons", "Clear search term"); - } - }} - /> + {!is1ColumnLayout &&
{searchInput}
}