From 082f3283f50fbe0d1663faf412d08bd543930b0a Mon Sep 17 00:00:00 2001
From: Zoe Roux
Date: Sun, 15 Feb 2026 13:18:36 +0100
Subject: [PATCH] Fix home page horizontal bars
---
front/bun.lock | 2 +-
front/src/components/entries/entry-box.tsx | 9 +-
front/src/components/items/context-menus.tsx | 4 +-
front/src/components/items/item-details.tsx | 18 ++--
front/src/components/items/item-grid.tsx | 16 ++-
front/src/components/items/item-list.tsx | 7 +-
front/src/query/fetch-infinite.tsx | 3 +-
front/src/ui/details/header.tsx | 1 -
front/src/ui/empty-view.tsx | 2 +-
front/src/ui/home/genre.tsx | 23 +----
front/src/ui/home/header.tsx | 97 +++++-------------
front/src/ui/home/news.tsx | 29 +-----
front/src/ui/home/recommended.tsx | 102 ++++++++++---------
front/src/ui/home/vertical.tsx | 26 ++---
front/src/ui/home/watchlist.tsx | 37 ++-----
front/src/ui/info/index.tsx | 17 ++--
front/src/ui/navbar.tsx | 18 ++--
17 files changed, 158 insertions(+), 253 deletions(-)
diff --git a/front/bun.lock b/front/bun.lock
index a85d45b0..3b81d2a7 100644
--- a/front/bun.lock
+++ b/front/bun.lock
@@ -442,7 +442,7 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
- "@legendapp/list": ["@legendapp/list@github:zoriya/legend-list#d5d3344", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "*" } }, "zoriya-legend-list-d5d3344"],
+ "@legendapp/list": ["@legendapp/list@github:zoriya/legend-list#c36ff94", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "*" } }, "zoriya-legend-list-c36ff94"],
"@material-symbols/svg-400": ["@material-symbols/svg-400@0.40.2", "", {}, "sha512-e2yEgZW/OveVT1sGaZW1kkRWTPVghjsJYWy+vIea3q08Fv2o7FCYv23PESMyr5D4AaAXdM5dKWkF1e6yIm4swA=="],
diff --git a/front/src/components/entries/entry-box.tsx b/front/src/components/entries/entry-box.tsx
index 30d0b87d..5df743f5 100644
--- a/front/src/components/entries/entry-box.tsx
+++ b/front/src/components/entries/entry-box.tsx
@@ -17,6 +17,7 @@ import { EntryContext } from "../items/context-menus";
import { ItemProgress } from "../items/item-grid";
export const EntryBox = ({
+ kind,
slug,
serieSlug,
name,
@@ -27,6 +28,7 @@ export const EntryBox = ({
className,
...props
}: {
+ kind: "movie" | "episode" | "special";
slug: string;
// if serie slug is null, disable "Go to serie" in the context menu
serieSlug: string | null;
@@ -44,7 +46,7 @@ export const EntryBox = ({
setMoreOpened(true)}
- className={cn("group w-[350px] items-center outline-0", className)}
+ className={cn("group w-[350px] items-center p-1 outline-0", className)}
{...props}
>
{
+EntryBox.Loader = (props: object) => {
return (
-
+
diff --git a/front/src/components/items/context-menus.tsx b/front/src/components/items/context-menus.tsx
index 708e4c18..8b4c5c8d 100644
--- a/front/src/components/items/context-menus.tsx
+++ b/front/src/components/items/context-menus.tsx
@@ -13,11 +13,13 @@ import { watchListIcon } from "./watchlist-info";
// import { useDownloader } from "../../packages/ui/src/downloads/ui/src/downloads";
export const EntryContext = ({
+ kind,
slug,
serieSlug,
className,
...props
}: {
+ kind: "movie" | "episode" | "special";
serieSlug: string | null;
slug: string;
className?: string;
@@ -38,7 +40,7 @@ export const EntryContext = ({
)}
{/* {
+} & ViewProps) => {
const [moreOpened, setMoreOpened] = useState(false);
const { t } = useTranslation();
@@ -82,8 +81,8 @@ export const ItemDetails = ({
seenCount={seenCount}
/>
-
-
+
+
{kind !== "collection" && (
{tagline}
}
-
+
{description ?? t("show.noOverview")}
@@ -142,10 +141,13 @@ export const ItemDetails = ({
);
};
-ItemDetails.Loader = (props: object) => {
+ItemDetails.Loader = ({ className, ...props }: ViewProps) => {
return (
diff --git a/front/src/components/items/item-grid.tsx b/front/src/components/items/item-grid.tsx
index 99abe57f..ff744a76 100644
--- a/front/src/components/items/item-grid.tsx
+++ b/front/src/components/items/item-grid.tsx
@@ -71,7 +71,6 @@ export const ItemGrid = ({
>
{
+ItemGrid.Loader = ({
+ horizontal = false,
+ ...props
+}: {
+ horizontal?: boolean;
+}) => {
return (
-
+
diff --git a/front/src/components/items/item-list.tsx b/front/src/components/items/item-list.tsx
index f751e1cb..fb8e6533 100644
--- a/front/src/components/items/item-list.tsx
+++ b/front/src/components/items/item-list.tsx
@@ -49,7 +49,8 @@ export const ItemList = ({
href={moreOpened ? undefined : href}
onLongPress={() => setMoreOpened(true)}
className={cn(
- "group h-80 w-full outline-0 ring-accent focus-within:ring-3 hover:ring-3",
+ "group m-1 mx-2 h-80 overflow-hidden rounded",
+ "outline-0 ring-accent focus-within:ring-3 hover:ring-3",
className,
)}
{...props}
@@ -57,7 +58,7 @@ export const ItemList = ({
@@ -111,7 +112,7 @@ export const ItemList = ({
ItemList.Loader = (props: object) => {
return (
diff --git a/front/src/query/fetch-infinite.tsx b/front/src/query/fetch-infinite.tsx
index b1fb3279..cd4d42e9 100644
--- a/front/src/query/fetch-infinite.tsx
+++ b/front/src/query/fetch-infinite.tsx
@@ -67,6 +67,8 @@ export const InfiniteFetch = ({
return isFetching ? [...items, ...placeholders] : items;
}, [items, isFetching, placeholderCount, numColumns]);
+ if (!data.length && Empty) return Empty;
+
return (
({
ItemSeparatorComponent={
Divider === true ? HR : (Divider as any) || undefined
}
- ListEmptyComponent={Empty}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
contentContainerStyle={{
diff --git a/front/src/ui/details/header.tsx b/front/src/ui/details/header.tsx
index 8a1fe486..a46fb7e0 100644
--- a/front/src/ui/details/header.tsx
+++ b/front/src/ui/details/header.tsx
@@ -166,7 +166,6 @@ export const TitleLine = ({
>
diff --git a/front/src/ui/empty-view.tsx b/front/src/ui/empty-view.tsx
index 82ad09e2..6dadce3e 100644
--- a/front/src/ui/empty-view.tsx
+++ b/front/src/ui/empty-view.tsx
@@ -10,7 +10,7 @@ export const EmptyView = ({
className?: string;
}) => {
return (
-
+
{message}
);
diff --git a/front/src/ui/home/genre.tsx b/front/src/ui/home/genre.tsx
index 7e80815d..d8ce05e3 100644
--- a/front/src/ui/home/genre.tsx
+++ b/front/src/ui/home/genre.tsx
@@ -1,28 +1,12 @@
import { useTranslation } from "react-i18next";
-import { View } from "react-native";
-import { useYoshiki } from "yoshiki/native";
import { ItemGrid, itemMap } from "~/components/items";
import { type Genre, Show } from "~/models";
-import { H3, ts } from "~/primitives";
+import { H3 } from "~/primitives";
import { InfiniteFetch, type QueryIdentifier } from "~/query";
import { EmptyView } from "~/ui/empty-view";
export const Header = ({ title }: { title: string }) => {
- const { css } = useYoshiki();
-
- return (
-
- {title}
-
- );
+ return {title}
;
};
export const GenreGrid = ({ genre }: { genre: Genre }) => {
@@ -34,10 +18,9 @@ export const GenreGrid = ({ genre }: { genre: Genre }) => {
}
Render={({ item }) => }
- Loader={ItemGrid.Loader}
+ Loader={() => }
/>
>
);
diff --git a/front/src/ui/home/header.tsx b/front/src/ui/home/header.tsx
index 66fbda93..cbcf947a 100644
--- a/front/src/ui/home/header.tsx
+++ b/front/src/ui/home/header.tsx
@@ -1,13 +1,10 @@
import Info from "@material-symbols/svg-400/rounded/info.svg";
import PlayArrow from "@material-symbols/svg-400/rounded/play_arrow-fill.svg";
-import { LinearGradient } from "expo-linear-gradient";
import type { ComponentProps } from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
-import { min, percent, px, rem, vh } from "yoshiki/native";
import { type KImage, Show } from "~/models";
import {
- ContrastArea,
H1,
H2,
IconButton,
@@ -17,7 +14,6 @@ import {
P,
Skeleton,
tooltip,
- ts,
} from "~/primitives";
import type { QueryIdentifier } from "~/query";
import { cn } from "~/utils";
@@ -93,72 +89,35 @@ Header.Loader = () => {
const { t } = useTranslation();
return (
-
- {({ css, theme }) => (
-
-
-
-
-
-
-
-
-
-
-
-
+
+ >
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/front/src/ui/home/news.tsx b/front/src/ui/home/news.tsx
index 66bf8288..f8241bc0 100644
--- a/front/src/ui/home/news.tsx
+++ b/front/src/ui/home/news.tsx
@@ -1,6 +1,5 @@
import { useTranslation } from "react-i18next";
import { EntryBox, entryDisplayNumber } from "~/components/entries";
-import { ItemGrid } from "~/components/items";
import { Entry } from "~/models";
import { InfiniteFetch, type QueryIdentifier } from "~/query";
import { EmptyView } from "~/ui/empty-view";
@@ -15,15 +14,11 @@ export const NewsList = () => {
- // x?.kind === "movie" || (!x && i % 2) ? "movie" : "episode"
- // }
- // getItemSizeMult={(_, __, kind) => (kind === "episode" ? 2 : 1)}
Empty={}
Render={({ item }) => {
- // if (item.kind === "episode" || item.kind === "special") {
return (
{
watchedPercent={item.progress.percent}
/>
);
- // }
- // return (
- //
- // );
}}
- Loader={({ index }) =>
- index % 2 ? :
- }
+ Loader={EntryBox.Loader}
/>
>
);
diff --git a/front/src/ui/home/recommended.tsx b/front/src/ui/home/recommended.tsx
index 4ce634ba..7d55ce56 100644
--- a/front/src/ui/home/recommended.tsx
+++ b/front/src/ui/home/recommended.tsx
@@ -1,59 +1,67 @@
import { useTranslation } from "react-i18next";
import { View } from "react-native";
-import { useYoshiki } from "yoshiki/native";
-import { ItemGrid } from "~/components/items";
import { ItemDetails } from "~/components/items/item-details";
import { Show } from "~/models";
-import { H3 } from "~/primitives";
-import { InfiniteFetch, type QueryIdentifier } from "~/query";
+import { useBreakpointMap } from "~/primitives";
+import { type QueryIdentifier, useInfiniteFetch } from "~/query";
import { getDisplayDate } from "~/utils";
+import { Header } from "./genre";
+
+const itemCount = 6;
export const Recommended = () => {
const { t } = useTranslation();
- const { css } = useYoshiki();
+ const { numColumns, gap } = useBreakpointMap(ItemDetails.layout);
+ const { items } = useInfiniteFetch(Recommended.query());
return (
-
- {t("home.recommended")}
- (
-
- )}
- Loader={ItemDetails.Loader}
- />
+
+
+
+ {[...Array(numColumns)].map((_, x) => (
+
+ {[...Array(itemCount / numColumns)].map((_, y) => {
+ if (!items) return ;
+ const item = items[x * (itemCount / numColumns) + y];
+ return (
+
+ );
+ })}
+
+ ))}
+
);
};
@@ -64,7 +72,7 @@ Recommended.query = (): QueryIdentifier => ({
path: ["api", "shows"],
params: {
sort: "random",
- limit: 6,
+ limit: itemCount,
with: ["firstEntry"],
},
});
diff --git a/front/src/ui/home/vertical.tsx b/front/src/ui/home/vertical.tsx
index 6a9a6c39..0e19e3b8 100644
--- a/front/src/ui/home/vertical.tsx
+++ b/front/src/ui/home/vertical.tsx
@@ -1,26 +1,22 @@
import { useTranslation } from "react-i18next";
import { View } from "react-native";
-import { useYoshiki } from "yoshiki/native";
-import { ItemGrid, ItemList, itemMap } from "~/components/items";
+import { ItemList, itemMap } from "~/components/items";
import { Show } from "~/models";
-import { H3 } from "~/primitives";
-import { InfiniteFetch, type QueryIdentifier } from "~/query";
+import { type QueryIdentifier, useInfiniteFetch } from "~/query";
+import { Header } from "./genre";
export const VerticalRecommended = () => {
const { t } = useTranslation();
- const { css } = useYoshiki();
+ const { items } = useInfiniteFetch(VerticalRecommended.query());
return (
-
- {t("home.recommended")}
- }
- Loader={() => }
- />
+
+
+
+ {items
+ ? items.map((x) => )
+ : [...Array(3)].map((_, i) => )}
+
);
};
diff --git a/front/src/ui/home/watchlist.tsx b/front/src/ui/home/watchlist.tsx
index 52ed9fd7..241a7af5 100644
--- a/front/src/ui/home/watchlist.tsx
+++ b/front/src/ui/home/watchlist.tsx
@@ -1,32 +1,29 @@
import { useTranslation } from "react-i18next";
import { View } from "react-native";
-import { useYoshiki } from "yoshiki/native";
import { EntryBox, entryDisplayNumber } from "~/components/entries";
-import { ItemGrid } from "~/components/items";
+import { ItemGrid, itemMap } from "~/components/items";
import { Show } from "~/models";
-import { Button, Link, P, ts } from "~/primitives";
+import { Button, Link, P } from "~/primitives";
import { useAccount } from "~/providers/account-context";
import { InfiniteFetch, type QueryIdentifier } from "~/query";
import { EmptyView } from "~/ui/empty-view";
-import { getDisplayDate } from "~/utils";
import { Header } from "./genre";
export const WatchlistList = () => {
const { t } = useTranslation();
- const { css } = useYoshiki();
const account = useAccount();
if (!account) {
return (
<>
-
+
{t("home.watchlistLogin")}
>
@@ -51,6 +48,7 @@ export const WatchlistList = () => {
if (entry) {
return (
{
/>
);
}
- return (
-
- );
+ return ;
}}
Loader={({ index }) =>
- index % 2 ? :
+ index % 2 ? :
}
/>
>
diff --git a/front/src/ui/info/index.tsx b/front/src/ui/info/index.tsx
index acfc3575..87c5f98d 100644
--- a/front/src/ui/info/index.tsx
+++ b/front/src/ui/info/index.tsx
@@ -1,6 +1,5 @@
import { useTranslation } from "react-i18next";
import { View } from "react-native";
-import { useYoshiki } from "yoshiki/native";
import { VideoInfo } from "~/models";
import { HR, P, Skeleton } from "~/primitives";
import { type QueryIdentifier, useFetch } from "~/query";
@@ -10,23 +9,19 @@ import { useQueryState } from "~/utils";
const formatBitrate = (b: number) => `${(b / 1000000).toFixed(2)} Mbps`;
const Row = ({ label, value }: { label: string; value: string }) => {
- const { css } = useYoshiki();
-
return (
-
- {label}
- {value}
+
+ {label}
+ {value}
);
};
Row.Loading = ({ label }: { label: string }) => {
- const { css } = useYoshiki();
-
return (
-
- {label}
-
+
+ {label}
+
);
};
diff --git a/front/src/ui/navbar.tsx b/front/src/ui/navbar.tsx
index 24e4bbc1..d48d2bdd 100644
--- a/front/src/ui/navbar.tsx
+++ b/front/src/ui/navbar.tsx
@@ -6,16 +6,10 @@ import Logout from "@material-symbols/svg-400/rounded/logout.svg";
import Search from "@material-symbols/svg-400/rounded/search-fill.svg";
import Settings from "@material-symbols/svg-400/rounded/settings.svg";
import { useIsFocused } from "@react-navigation/native";
-import {
- useGlobalSearchParams,
- useNavigation,
- usePathname,
- useRouter,
-} from "expo-router";
+import { useNavigation, usePathname, useRouter } from "expo-router";
import KyooLongLogo from "public/icon-long.svg";
import {
type ComponentProps,
- type Ref,
useEffect,
useLayoutEffect,
useRef,
@@ -24,9 +18,8 @@ import {
import { useTranslation } from "react-i18next";
import {
Platform,
- TextInput,
type PressableProps,
- type TextInputProps,
+ TextInput,
View,
type ViewProps,
} from "react-native";
@@ -43,7 +36,6 @@ import {
Avatar,
HR,
IconButton,
- Input,
Link,
Menu,
PressableFeedback,
@@ -146,9 +138,10 @@ const SearchBar = () => {
const [query, setQuery] = useState("");
const path = usePathname();
+ const shouldExpand = useRef(false);
useEffect(() => {
- console.log(path);
- if (path === "/browse") {
+ if (path === "/browse" && shouldExpand.current) {
+ shouldExpand.current = false;
// Small delay to allow animation to start before focusing
setTimeout(() => {
setExpanded(true);
@@ -207,6 +200,7 @@ const SearchBar = () => {
setQuery("");
router.setParams({ q: undefined });
} else {
+ shouldExpand.current = true;
setExpanded(true);
// Small delay to allow animation to start before focusing
setTimeout(() => inputRef.current?.focus(), 100);