diff --git a/front/apps/web/src/pages/_app.tsx b/front/apps/web/src/pages/_app.tsx index 97d5fd94..2c097bb2 100755 --- a/front/apps/web/src/pages/_app.tsx +++ b/front/apps/web/src/pages/_app.tsx @@ -66,6 +66,10 @@ const GlobalCssTheme = () => { *:hover::-webkit-scrollbar-thumb { background-color: rgb(134, 127, 127); } + + #__next { + height: 100vh; + } `} diff --git a/front/packages/primitives/src/index.ts b/front/packages/primitives/src/index.ts index bb9b44da..a40a5892 100644 --- a/front/packages/primitives/src/index.ts +++ b/front/packages/primitives/src/index.ts @@ -28,6 +28,7 @@ export * from "./image"; export * from "./skeleton"; export * from "./tooltip"; +export * from "./utils/breakpoints"; export * from "./utils/nojs"; import { Dimensions } from "react-native"; diff --git a/front/packages/primitives/src/utils/breakpoints.ts b/front/packages/primitives/src/utils/breakpoints.ts new file mode 100644 index 00000000..3f2faf12 --- /dev/null +++ b/front/packages/primitives/src/utils/breakpoints.ts @@ -0,0 +1,62 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { useWindowDimensions } from "react-native"; +import { AtLeastOne, Breakpoints as YoshikiBreakpoint } from "yoshiki/dist/type"; +import { isBreakpoints } from "yoshiki/dist/utils"; +import { breakpoints } from "yoshiki/native"; + +export type Breakpoint = T | AtLeastOne>; + +// copied from yoshiki. +const useBreakpoint = () => { + const { width } = useWindowDimensions(); + const idx = Object.values(breakpoints).findIndex((x) => width <= x); + if (idx === -1) return 0; + return idx - 1; +}; + +const getBreakpointValue = (value: Breakpoint, breakpoint: number): T => { + if (!isBreakpoints(value)) return value; + const bpKeys = Object.keys(breakpoints) as Array>; + for (let i = breakpoint; i >= 0; i--) { + if (bpKeys[i] in value) { + const val = value[bpKeys[i]]; + if (val) return val; + } + } + // This should never be reached. + return undefined!; +}; + +export const useBreakpointValue = (value: Breakpoint): T => { + const breakpoint = useBreakpoint(); + return getBreakpointValue(value, breakpoint); +}; + +export const useBreakpointMap = >( + value: T, +): { [key in keyof T]: T[key] extends Breakpoint ? V : T } => { + const breakpoint = useBreakpoint(); + // @ts-ignore + return Object.fromEntries( + Object.entries(value).map(([key, val]) => [key, getBreakpointValue(val, breakpoint)]), + ); +}; diff --git a/front/packages/ui/src/browse/grid.tsx b/front/packages/ui/src/browse/grid.tsx index 047a1382..560f599b 100644 --- a/front/packages/ui/src/browse/grid.tsx +++ b/front/packages/ui/src/browse/grid.tsx @@ -21,7 +21,7 @@ import { Link, Skeleton, Poster, ts, P, SubP } from "@kyoo/primitives"; import { Platform } from "react-native"; import { percent, px, Stylable, useYoshiki } from "yoshiki/native"; -import { WithLoading } from "../fetch"; +import { Layout, WithLoading } from "../fetch"; export const ItemGrid = ({ href, @@ -85,4 +85,7 @@ export const ItemGrid = ({ ); }; -ItemGrid.height = px(250); +ItemGrid.layout = { + size: px(150), + numColumns: { xs: 3, md: 5, xl: 7 }, +} satisfies Layout; diff --git a/front/packages/ui/src/browse/index.tsx b/front/packages/ui/src/browse/index.tsx index 72a7bc47..aa1027ab 100644 --- a/front/packages/ui/src/browse/index.tsx +++ b/front/packages/ui/src/browse/index.tsx @@ -31,6 +31,7 @@ import { DefaultLayout } from "../layout"; import { WithLoading } from "../fetch"; import { InfiniteFetch } from "../fetch-infinite"; import { ItemGrid } from "./grid"; +import { ItemList } from "./list"; import { SortBy, SortOrd, Layout } from "./types"; const itemMap = (item: WithLoading): WithLoading> => { @@ -71,6 +72,8 @@ export const BrowsePage: QueryPage<{ slug?: string }> = ({ slug }) => { const [sortOrd, setSortOrd] = useState(SortOrd.Asc); const [layout, setLayout] = useState(Layout.Grid); + const LayoutComponent = layout === Layout.Grid ? ItemGrid : ItemList; + return ( <> {/* = ({ slug }) => { - {(item, key) => } + {(item, key) => } ); diff --git a/front/packages/ui/src/fetch-infinite.tsx b/front/packages/ui/src/fetch-infinite.tsx index d09adf4e..59e727d4 100644 --- a/front/packages/ui/src/fetch-infinite.tsx +++ b/front/packages/ui/src/fetch-infinite.tsx @@ -19,30 +19,30 @@ */ import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models"; +import { useBreakpointMap } from "@kyoo/primitives"; import { FlashList } from "@shopify/flash-list"; import { ReactElement } from "react"; -import { ErrorView, WithLoading } from "./fetch"; +import { ErrorView, Layout, WithLoading } from "./fetch"; export const InfiniteFetch = ({ query, placeholderCount = 15, children, - size, - numColumns, + layout, ...props }: { query: QueryIdentifier; placeholderCount?: number; - numColumns: number; + layout: Layout; children: ( item: Data extends Page ? WithLoading : WithLoading, key: string | undefined, i: number, ) => ReactElement | null; - size: number; }): JSX.Element | null => { if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch."); + const { numColumns, size } = useBreakpointMap(layout); const { items, error, fetchNextPage, hasNextPage, refetch, isRefetching } = useInfiniteFetch(query); diff --git a/front/packages/ui/src/fetch-infinite.web.tsx b/front/packages/ui/src/fetch-infinite.web.tsx index 5a0c6999..f1d2a5e3 100644 --- a/front/packages/ui/src/fetch-infinite.web.tsx +++ b/front/packages/ui/src/fetch-infinite.web.tsx @@ -19,19 +19,22 @@ */ import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models"; +import { useBreakpointValue } from "@kyoo/primitives"; import { ReactElement } from "react"; import InfiniteScroll from "react-infinite-scroll-component"; import { useYoshiki } from "yoshiki"; -import { ErrorView, WithLoading } from "./fetch"; +import { ErrorView, Layout, WithLoading } from "./fetch"; export const InfiniteFetch = ({ query, placeholderCount = 15, children, + layout, ...props }: { query: QueryIdentifier; placeholderCount?: number; + layout: Layout; children: ( item: Data extends Page ? WithLoading : WithLoading, key: string | undefined, @@ -41,23 +44,33 @@ export const InfiniteFetch = ({ if (!query.infinite) console.warn("A non infinite query was passed to an InfiniteFetch."); const { items, error, fetchNextPage, hasNextPage } = useInfiniteFetch(query); + const { numColumns } = useBreakpointValue(layout); const { css } = useYoshiki(); if (error) return ; return ( children({ isLoading: true } as any, i.toString(), i))} {...css( - { - display: "flex", - flexWrap: "wrap", - alignItems: "flex-start", - justifyContent: "center", - }, + [ + { + display: "flex", + alignItems: "flex-start", + justifyContent: "center", + }, + numColumns === 1 && { + flexDirection: "column", + }, + numColumns !== 1 && { + flexWrap: "wrap", + }, + ], + props, )} > diff --git a/front/packages/ui/src/fetch.tsx b/front/packages/ui/src/fetch.tsx index 6ec4a2ca..49f4a074 100644 --- a/front/packages/ui/src/fetch.tsx +++ b/front/packages/ui/src/fetch.tsx @@ -19,10 +19,12 @@ */ import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models"; -import { P } from "@kyoo/primitives"; +import { Breakpoint, P } from "@kyoo/primitives"; import { View } from "react-native"; import { useYoshiki } from "yoshiki/native"; +export type Layout = { numColumns: Breakpoint; size: Breakpoint }; + export type WithLoading = | (Item & { isLoading: false }) | (Partial & { isLoading: true }); diff --git a/front/packages/ui/src/layout.tsx b/front/packages/ui/src/layout.tsx index ff8c6e04..3ff4e57a 100644 --- a/front/packages/ui/src/layout.tsx +++ b/front/packages/ui/src/layout.tsx @@ -28,7 +28,9 @@ export const DefaultLayout = (page: ReactElement) => { return ( <> -
{page}
+
+ {page} +
); };