Added easy access to search input on mobile pages

This commit is contained in:
DrMint 2023-06-12 09:53:55 +02:00
parent d560008cff
commit 22e1bf4842
22 changed files with 326 additions and 287 deletions

View File

@ -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";
/*

View File

@ -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<NodeJS.Timeout>();
return (
<div className="grid">
<div
id={Ids.ContentsFolderPath}
onPointerEnter={() => {
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" ? (
<Button href="/contents" icon="home" active={index === path.length - 1} />
) : (
<TranslatedButton
className="w-max"
href={`/contents/folder/${pathFolder.slug}`}
translations={filterHasAttributes(pathFolder.titles, [
"language.data.attributes.code",
]).map((title) => ({
language: title.language.data.attributes.code,
text: title.title,
}))}
fallback={{
text: prettySlug(pathFolder.slug),
}}
active={index === path.length - 1}
/>
)}
{index < path.length - 1 && <Ico icon="chevron_right" />}
</>
))}
</div>
</div>
);
};

View File

@ -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)) && (
<div className={className}>
<Button href={href} text={format("return_to_x", { x: title })} icon="navigate_before" />
</div>
)}
</>
<div className={cJoin("mx-auto w-full max-w-lg place-self-center", className)}>
<Button href={href} text={format("return_to_x", { x: title })} icon="navigate_before" />
</div>
);
};

View File

@ -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 = (
<ContentPanel>
{returnHref && returnTitle && (
<ReturnButton
href={returnHref}
title={returnTitle}
displayOnlyOn={"1ColumnLayout"}
className="mb-10"
/>
{is1ColumnLayout && returnHref && returnTitle && (
<ReturnButton href={returnHref} title={returnTitle} className="mb-10" />
)}
{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 ? (
<LanguageSwitcher {...languageSwitcherProps} />

View File

@ -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 = ({
</div>
)}
{releaseDate && (
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{format("release_date")}</h3>
<div className="flex flex-row flex-wrap">
<Chip text={formatDate(releaseDate)} />
</div>
</div>
)}
{categories && categories.length > 0 && (
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{format("category", { count: categories.length })}</h3>

View File

@ -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]);
};

View File

@ -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 = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos/Channel", "Change search term");
} else {
sendAnalytics("Videos/Channel", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<ReturnButton
href="/archives/videos"
title={format("videos")}
displayOnlyOn={"3ColumnsLayout"}
className="mb-10"
/>
{!is1ColumnLayout && (
<ReturnButton href="/archives/videos" title={format("videos")} className="mb-10" />
)}
<PanelHeader
icon="movie"
@ -166,20 +182,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos", "Change search term");
} else {
sendAnalytics("Videos", "Clear search term");
}
}}
/>
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<WithLabel label={format("order_by")}>
<Select
@ -190,7 +193,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
setPage(1);
setSortingMethod(newSort);
sendAnalytics(
"Videos",
"Videos/Channel",
`Change sorting method (${
sortingMethods.map((item) => item.meiliAttribute)[newSort]
})`
@ -224,7 +227,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
setQuery(DEFAULT_FILTERS_STATE.searchName);
setSortingMethod(DEFAULT_FILTERS_STATE.sortingMethod);
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
sendAnalytics("Videos", "Reset all filters");
sendAnalytics("Videos/Channel", "Reset all filters");
}}
/>
</SubPanel>
@ -232,6 +235,7 @@ const Channel = ({ channel, ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={videos?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -25,6 +25,8 @@ import { Button } from "components/Inputs/Button";
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";
/*
*
@ -57,6 +59,7 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
const { format } = useFormat();
const hoverable = useDeviceSupportsHover();
const router = useTypedRouter(queryParamSchema);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const sortingMethods = useMemo(
() => [
@ -141,14 +144,25 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos", "Change search term");
} else {
sendAnalytics("Videos", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<ReturnButton
href="/archives/"
title={"Archives"}
displayOnlyOn={"3ColumnsLayout"}
className="mb-10"
/>
{!is1ColumnLayout && <ReturnButton href="/archives/" title={"Archives"} className="mb-10" />}
<PanelHeader
icon="movie"
@ -158,20 +172,7 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(newQuery) => {
setPage(1);
setQuery(newQuery);
if (isDefinedAndNotEmpty(newQuery)) {
sendAnalytics("Videos", "Change search term");
} else {
sendAnalytics("Videos", "Clear search term");
}
}}
/>
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<WithLabel label={format("order_by")}>
<Select
@ -226,6 +227,7 @@ const Videos = ({ ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={videos?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -34,19 +34,19 @@ interface Props extends AppLayoutRequired {
const Video = ({ video, ...otherProps }: Props): JSX.Element => {
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
const { format, formatDate } = useFormat();
const subPanel = (
<SubPanel>
<ReturnButton
href="/archives/videos/"
title={format("videos")}
displayOnlyOn={"3ColumnsLayout"}
/>
<HorizontalLine />
{!is1ColumnLayout && (
<>
<ReturnButton href="/archives/videos/" title={format("videos")} />
<HorizontalLine />
</>
)}
<NavOption title={format("video")} url="#video" border onClick={closeSubPanel} />
<NavOption title={format("channel")} url="#channel" border onClick={closeSubPanel} />
@ -56,12 +56,9 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<ReturnButton
href="/library/"
title={format("library")}
displayOnlyOn={"1ColumnLayout"}
className="mb-10"
/>
{is1ColumnLayout && (
<ReturnButton href="/library/" title={format("library")} className="mb-10" />
)}
<div className="grid place-items-center gap-12">
<div id="video" className="w-full overflow-hidden rounded-xl shadow-xl shadow-shade/80">

View File

@ -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 = (
<SubPanel>
<ReturnButton
displayOnlyOn={"3ColumnsLayout"}
href="/chronicles"
title={format("chronicles")}
/>
<HorizontalLine />
{!is1ColumnLayout && (
<>
<ReturnButton href="/chronicles" title={format("chronicles")} />
<HorizontalLine />
</>
)}
<ChroniclesLists chapters={chapters} currentChronicleSlug={chronicle.slug} />
</SubPanel>
);
const contentPanel = (
<ContentPanel>
<ReturnButton
displayOnlyOn={"1ColumnLayout"}
href="/chronicles"
title={format("chronicles")}
className="mb-10"
/>
{is1ColumnLayout && (
<ReturnButton href="/chronicles" title={format("chronicles")} className="mb-10" />
)}
{isDefined(selectedTranslation) ? (
<>

View File

@ -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 = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<TranslatedReturnButton
{...returnButtonProps}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
{is1ColumnLayout && <TranslatedReturnButton {...returnButtonProps} className="mb-10" />}
<div className="grid place-items-center">
<ElementsSeparator

View File

@ -64,6 +64,7 @@ const Contents = (props: Props): JSX.Element => {
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 = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Contents/All", "Change search term");
} else {
sendAnalytics("Contents/All", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -171,20 +188,8 @@ const Contents = (props: Props): JSX.Element => {
<HorizontalLine />
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Contents/All", "Change search term");
} else {
sendAnalytics("Contents/All", "Clear search term");
}
}}
/>
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<WithLabel label={format("order_by")}>
<Select
@ -252,6 +257,7 @@ const Contents = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={contents?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -1,5 +1,4 @@
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
import { useMemo } from "react";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel";
import { getOpenGraph } from "helpers/openGraph";
@ -8,8 +7,7 @@ import { filterHasAttributes } from "helpers/asserts";
import { GetContentsFolderQuery, ParentFolderPreviewFragment } from "graphql/generated";
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
import { prettySlug } from "helpers/formatters";
import { Ico } from "components/Ico";
import { Button, TranslatedButton } from "components/Inputs/Button";
import { Button } from "components/Inputs/Button";
import { PanelHeader } from "components/PanelComponents/PanelHeader";
import { SubPanel } from "components/Containers/SubPanel";
import { TranslatedPreviewCard } from "components/PreviewCard";
@ -21,6 +19,7 @@ import { TranslatedPreviewFolder } from "components/Contents/PreviewFolder";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { Chip } from "components/Chip";
import { FolderPath } from "components/Contents/FolderPath";
/*
*
@ -39,14 +38,6 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
const filteredPath = useMemo(
() =>
path.filter(
(_, index) => isContentPanelAtLeast4xl || index === 0 || index === path.length - 1
),
[path, isContentPanelAtLeast4xl]
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -68,30 +59,7 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<div className="mb-10 flex flex-wrap place-items-center justify-start gap-x-1 gap-y-4">
{filteredPath.map((pathFolder, index) => (
<>
{pathFolder.slug === "root" ? (
<Button href="/contents" icon="home" active={index === filteredPath.length - 1} />
) : (
<TranslatedButton
href={`/contents/folder/${pathFolder.slug}`}
translations={filterHasAttributes(pathFolder.titles, [
"language.data.attributes.code",
]).map((title) => ({
language: title.language.data.attributes.code,
text: title.title,
}))}
fallback={{
text: prettySlug(pathFolder.slug),
}}
active={index === filteredPath.length - 1}
/>
)}
{index < filteredPath.length - 1 && <Ico icon="chevron_right" />}
</>
))}
</div>
<FolderPath path={path} />
{folder.subfolders?.data && folder.subfolders.data.length > 0 && (
<div className="mb-8">
@ -101,7 +69,7 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
</div>
<div
className={cJoin(
"grid items-start gap-8 pb-12",
"grid items-start pb-12",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",
@ -133,7 +101,7 @@ const ContentsFolder = ({ openGraph, folder, path, ...otherProps }: Props): JSX.
</div>
<div
className={cJoin(
"grid items-start gap-8 pb-12",
"grid items-start pb-12",
cIf(
isContentPanelAtLeast4xl,
"grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] gap-x-6 gap-y-8",

View File

@ -29,7 +29,7 @@ import {
isDefined,
isDefinedAndNotEmpty,
} from "helpers/asserts";
import { useScrollTopOnChange } from "hooks/useScrollTopOnChange";
import { useScrollTopOnChange } from "hooks/useScrollOnChange";
import { getScanArchiveURL, getTrackURL, isUntangibleGroupItem } from "helpers/libraryItem";
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel";
@ -95,7 +95,7 @@ const LibrarySlug = ({
const isContentPanelAtLeast3xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast3xl);
const isContentPanelAtLeastSm = useAtomGetter(atoms.containerQueries.isContentPanelAtLeastSm);
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const hoverable = useDeviceSupportsHover();
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
@ -111,13 +111,8 @@ const LibrarySlug = ({
<SubPanel>
<ElementsSeparator>
{[
is3ColumnsLayout && (
<ReturnButton
key="ReturnButton"
href="/library/"
title={format("library")}
displayOnlyOn="3ColumnsLayout"
/>
!is1ColumnLayout && (
<ReturnButton key="ReturnButton" href="/library/" title={format("library")} />
),
<div className="grid gap-4" key="NavOption">
<NavOption
@ -177,12 +172,10 @@ const LibrarySlug = ({
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<ReturnButton
href="/library/"
title={format("library")}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
{is1ColumnLayout && (
<ReturnButton href="/library/" title={format("library")} className="mb-10" />
)}
<div className="grid place-items-center gap-12">
<div
className={cJoin(

View File

@ -33,6 +33,8 @@ import { useLibraryItemUserStatus } from "hooks/useLibraryItemUserStatus";
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";
/*
*
@ -71,6 +73,7 @@ const Library = (props: Props): JSX.Element => {
const hoverable = useDeviceSupportsHover();
const { format, formatCategory, formatLibraryItemSubType } = useFormat();
const { libraryItemUserStatus } = useLibraryItemUserStatus();
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const sortingMethods = useMemo(
() => [
@ -234,6 +237,22 @@ const Library = (props: Props): JSX.Element => {
if (isDefined(totalPages) && totalPages < page && totalPages >= 1) setPage(totalPages);
}, [libraryItems?.totalPages, page]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Library", "Change search term");
} else {
sendAnalytics("Library", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -244,20 +263,7 @@ const Library = (props: Props): JSX.Element => {
<HorizontalLine />
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Library", "Change search term");
} else {
sendAnalytics("Library", "Clear search term");
}
}}
/>
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<WithLabel label={format("order_by")}>
<Select
@ -388,6 +394,7 @@ const Library = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={libraryItems?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(12rem,1fr))] items-end

View File

@ -63,6 +63,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
const hoverable = useDeviceSupportsHover();
const router = useTypedRouter(queryParamSchema);
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
@ -136,6 +137,22 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("News", "Change search term");
} else {
sendAnalytics("News", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -146,20 +163,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
<HorizontalLine />
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("News", "Change search term");
} else {
sendAnalytics("News", "Clear search term");
}
}}
/>
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<WithLabel label={format("language", { count: Infinity })}>
<Select
@ -208,6 +212,7 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={posts?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -39,6 +39,7 @@ interface Props extends AppLayoutRequired {
const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
const { format, formatCategory, formatWikiTag } = useFormat();
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const router = useRouter();
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
@ -51,41 +52,30 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
[]
),
});
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
const toc = getTocFromMarkdawn(selectedTranslation?.body?.body, selectedTranslation?.title);
const subPanel = is3ColumnsLayout ? (
<SubPanel>
<ElementsSeparator>
{[
<ReturnButton
key="return"
href={`/wiki`}
title={format("wiki")}
displayOnlyOn={"3ColumnsLayout"}
/>,
toc && (
<TableOfContents
key="toc"
toc={toc}
onContentClicked={() => setSubPanelOpened(false)}
/>
),
]}
</ElementsSeparator>
</SubPanel>
) : undefined;
const subPanel =
toc || !is1ColumnLayout ? (
<SubPanel>
<ElementsSeparator>
{[
!is1ColumnLayout && <ReturnButton key="return" href={`/wiki`} title={format("wiki")} />,
toc && (
<TableOfContents
key="toc"
toc={toc}
onContentClicked={() => setSubPanelOpened(false)}
/>
),
]}
</ElementsSeparator>
</SubPanel>
) : undefined;
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ReturnButton
href={`/wiki`}
title={format("wiki")}
displayOnlyOn={"1ColumnLayout"}
className="mb-10"
/>
{is1ColumnLayout && <ReturnButton href={`/wiki`} title={format("wiki")} className="mb-10" />}
<div className="flex flex-wrap place-content-center gap-3">
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
{selectedTranslation?.aliases && selectedTranslation.aliases.length > 0 && (
@ -103,7 +93,7 @@ const WikiPage = ({ page, ...otherProps }: Props): JSX.Element => {
<div
className={cJoin(
"mb-8 overflow-hidden rounded-lg bg-mid text-center",
cIf(is3ColumnsLayout, "float-right ml-8 w-96")
cIf(!is1ColumnLayout, "float-right ml-8 w-96")
)}>
{page.thumbnail?.data?.attributes && (
<Img

View File

@ -27,7 +27,7 @@ import { useIntersectionList } from "hooks/useIntersectionList";
import { HorizontalLine } from "components/HorizontalLine";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomSetter } from "helpers/atoms";
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
@ -43,6 +43,7 @@ interface Props extends AppLayoutRequired {
const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props): JSX.Element => {
const { format } = useFormat();
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
const ids = useMemo(
() => filterHasAttributes(chronologyEras, ["attributes"]).map((era) => era.attributes.slug),
@ -53,7 +54,7 @@ const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props):
const subPanel = (
<SubPanel>
<ReturnButton href="/wiki" title={format("wiki")} displayOnlyOn="3ColumnsLayout" />
{!is1ColumnLayout && <ReturnButton href="/wiki" title={format("wiki")} />}
<HorizontalLine />
@ -83,12 +84,7 @@ const Chronology = ({ chronologyItems, chronologyEras, ...otherProps }: Props):
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/wiki"
title={format("wiki")}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
{is1ColumnLayout && <ReturnButton href="/wiki" title={format("wiki")} className="mb-10" />}
{filterHasAttributes(chronologyEras, ["attributes"]).map((era) => (
<TranslatedChronologyEra

View File

@ -29,7 +29,7 @@ import {
import { Paginator } from "components/Containers/Paginator";
import { useFormat } from "hooks/useFormat";
import { getFormat } from "helpers/i18n";
import { useAtomSetter } from "helpers/atoms";
import { useAtomGetter, useAtomSetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
import { Select } from "components/Inputs/Select";
@ -64,6 +64,7 @@ const Wiki = (props: Props): JSX.Element => {
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
const router = useTypedRouter(queryParamSchema);
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
const languageOptions = useMemo(() => {
const memo =
@ -137,6 +138,22 @@ const Wiki = (props: Props): JSX.Element => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.isReady]);
const searchInput = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Wiki", "Change search term");
} else {
sendAnalytics("Wiki", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<PanelHeader
@ -147,20 +164,7 @@ const Wiki = (props: Props): JSX.Element => {
<HorizontalLine />
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Wiki", "Change search term");
} else {
sendAnalytics("Wiki", "Clear search term");
}
}}
/>
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<WithLabel label={format("language", { count: Infinity })}>
<Select
@ -226,6 +230,7 @@ const Wiki = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{is1ColumnLayout && <div className="mx-auto mb-12 max-w-lg">{searchInput}</div>}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={wikiPages?.totalPages}>
<div
className="grid grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-start

View File

@ -73,17 +73,19 @@ const WeaponPage = ({ weapon, primaryName, aliases, ...otherProps }: Props): JSX
const currentIntersection = useIntersectionList(intersectionIds);
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
const searchInput = (
<ReturnButton
key="return-button"
href="/wiki/weapons"
title={format("weapon", { count: Infinity })}
/>
);
const subPanel = (
<SubPanel>
<ElementsSeparator>
{[
is3ColumnsLayout && (
<ReturnButton
key="return-button"
href="/wiki/weapons"
title={format("weapon", { count: Infinity })}
/>
),
is3ColumnsLayout && searchInput,
<Fragment key="nav-options">
{intersectionIds.map((id, index) => (
@ -126,12 +128,8 @@ const WeaponPage = ({ weapon, primaryName, aliases, ...otherProps }: Props): JSX
const contentPanel = (
<ContentPanel>
<ReturnButton
href="/wiki/weapons"
title={format("weapon", { count: Infinity })}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
{!is3ColumnsLayout && <div className="mb-10">{searchInput}</div>}
<ThumbnailHeader
title={primaryName}
subtitle={aliases.join("・")}

View File

@ -30,6 +30,8 @@ import { WithLabel } from "components/Inputs/WithLabel";
import { Switch } from "components/Inputs/Switch";
import { ReturnButton } from "components/PanelComponents/ReturnButton";
import { Select } from "components/Inputs/Select";
import { useAtomGetter } from "helpers/atoms";
import { atoms } from "contexts/atoms";
/*
*
@ -60,6 +62,7 @@ const Weapons = (props: Props): JSX.Element => {
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 = (
<TextInput
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Weapons", "Change search term");
} else {
sendAnalytics("Weapons", "Clear search term");
}
}}
/>
);
const subPanel = (
<SubPanel>
<ReturnButton
href="/wiki"
title={format("wiki")}
displayOnlyOn="3ColumnsLayout"
className="mb-10"
/>
{!is1ColumnLayout && <ReturnButton href="/wiki" title={format("wiki")} className="mb-10" />}
<PanelHeader
icon="shield"
title={format("weapon", { count: Infinity })}
description={format("weapons_description")}
/>
<HorizontalLine />
<TextInput
className="mb-6 w-full"
placeholder={format("search_placeholder")}
value={query}
onChange={(name) => {
setPage(1);
setQuery(name);
if (isDefinedAndNotEmpty(name)) {
sendAnalytics("Weapons", "Change search term");
} else {
sendAnalytics("Weapons", "Clear search term");
}
}}
/>
{!is1ColumnLayout && <div className="mb-6">{searchInput}</div>}
<WithLabel label={format("language", { count: Infinity })}>
<Select
@ -210,12 +212,12 @@ const Weapons = (props: Props): JSX.Element => {
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
<ReturnButton
href="/wiki"
title={format("wiki")}
displayOnlyOn="1ColumnLayout"
className="mb-10"
/>
{is1ColumnLayout && (
<>
<ReturnButton href="/wiki" title={format("wiki")} className="mb-6" />
<div className="mx-auto mb-12 max-w-lg">{searchInput}</div>
</>
)}
<Paginator page={page} onPageChange={setPage} totalNumberOfPages={weapons?.totalPages}>
<div

View File

@ -3,4 +3,5 @@ export enum Ids {
ContentPanel = "contentPanel495922447721572",
SubPanel = "subPanelz9e8rs2d3f18zer98ze",
LightBox = "lightBoxqsd564az89e732s1",
ContentsFolderPath = "contentsfolderpath8zer6az4",
}