From 25b4e95128f399c9c0dbf2764f374fce0444a6d1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 16 Jan 2023 16:15:37 +0900 Subject: [PATCH] Add focus handling for the details page --- front/apps/mobile/app/_layout.tsx | 2 +- front/apps/mobile/package.json | 2 +- front/apps/web/package.json | 2 +- front/packages/primitives/src/icons.tsx | 38 ++-- front/packages/primitives/src/links.tsx | 35 ++-- .../primitives/src/themes/catppuccin.ts | 6 +- .../packages/primitives/src/themes/theme.tsx | 1 - front/packages/ui/src/browse/grid.tsx | 2 +- front/packages/ui/src/details/episode.tsx | 25 ++- front/packages/ui/src/details/header.tsx | 175 +++++++++++------- front/packages/ui/src/details/season.tsx | 4 +- front/packages/ui/src/details/show.tsx | 4 +- front/packages/ui/src/layout.tsx | 9 +- front/packages/ui/src/navbar/index.tsx | 2 +- front/yarn.lock | 12 +- 15 files changed, 197 insertions(+), 122 deletions(-) diff --git a/front/apps/mobile/app/_layout.tsx b/front/apps/mobile/app/_layout.tsx index 789bac1f..01480b45 100644 --- a/front/apps/mobile/app/_layout.tsx +++ b/front/apps/mobile/app/_layout.tsx @@ -67,7 +67,7 @@ const ThemedStack = ({ onLayout }: { onLayout?: () => void }) => { backgroundColor: theme.background, }, headerStyle: { - backgroundColor: theme.appbar, + backgroundColor: theme.accent, }, headerTintColor: theme.colors.white, }} diff --git a/front/apps/mobile/package.json b/front/apps/mobile/package.json index aba1210a..91c1af83 100644 --- a/front/apps/mobile/package.json +++ b/front/apps/mobile/package.json @@ -46,7 +46,7 @@ "react-native-screens": "~3.18.0", "react-native-svg": "13.4.0", "react-native-video": "alpha", - "yoshiki": "1.2.0" + "yoshiki": "1.2.1" }, "devDependencies": { "@babel/core": "^7.19.3", diff --git a/front/apps/web/package.json b/front/apps/web/package.json index 39d987f7..30b91d42 100644 --- a/front/apps/web/package.json +++ b/front/apps/web/package.json @@ -36,7 +36,7 @@ "react-native-web": "^0.18.10", "solito": "^2.0.5", "superjson": "^1.11.0", - "yoshiki": "1.2.0", + "yoshiki": "1.2.1", "zod": "^3.19.1" }, "devDependencies": { diff --git a/front/packages/primitives/src/icons.tsx b/front/packages/primitives/src/icons.tsx index 07ec5363..35aaaee1 100644 --- a/front/packages/primitives/src/icons.tsx +++ b/front/packages/primitives/src/icons.tsx @@ -22,9 +22,10 @@ import React, { ComponentProps, ComponentType, ForwardedRef, forwardRef } from " import { Platform, PressableProps, ViewStyle } from "react-native"; import { SvgProps } from "react-native-svg"; import { YoshikiStyle } from "yoshiki/dist/type"; -import { px, useYoshiki } from "yoshiki/native"; +import { px, Theme, useYoshiki } from "yoshiki/native"; import { PressableFeedback } from "./links"; -import { ts } from "./utils"; +import { alpha } from "./themes"; +import { Breakpoint, ts, useBreakpointValue } from "./utils"; declare module "react" { function forwardRef( @@ -34,24 +35,25 @@ declare module "react" { type IconProps = { icon: ComponentType; - color?: YoshikiStyle; + color?: Breakpoint; size?: YoshikiStyle; }; export const Icon = ({ icon: Icon, color, size = 24, ...props }: IconProps) => { const { css, theme } = useYoshiki(); - const computed = css( - { width: size, height: size, fill: color ?? theme.contrast } as ViewStyle, - props, - ); + // eslint-disable-next-line react-hooks/rules-of-hooks + const colorValue = Platform.OS !== "web" ? useBreakpointValue(color) : null; + return ( ({ - web: computed, + web: css({ width: size, height: size, fill: color ?? theme.contrast } as ViewStyle, props), default: { - height: computed.style?.height, - width: computed.style?.width, - ...computed, + height: size, + width: size, + // @ts-ignore + fill: colorValue ?? theme.contrast, + ...props, }, })} /> @@ -78,11 +80,17 @@ export const IconButton = forwardRef(function _IconButton alpha(theme.contrast, 0.5), + }, + }, }, asProps, ) as AsProps)} @@ -99,10 +107,16 @@ export const IconFab = ( return ( theme.accent, + fover: { + self: { + transform: [{ scale: 1.3 }], + bg: (theme: Theme) => theme.accent, + }, + }, }, props, ) as any)} diff --git a/front/packages/primitives/src/links.tsx b/front/packages/primitives/src/links.tsx index bc8cceac..a7a7f1e8 100644 --- a/front/packages/primitives/src/links.tsx +++ b/front/packages/primitives/src/links.tsx @@ -50,25 +50,24 @@ export const A = ({ ); }; -export const PressableFeedback = forwardRef(function _Feedback( - { children, ...props }, - ref, -) { - const theme = useTheme(); +export const PressableFeedback = forwardRef( + function _Feedback({ children, ...props }, ref) { + const theme = useTheme(); - return ( - - {children} - - ); -}); + return ( + + {children} + + ); + }, +); export const Link = ({ href, diff --git a/front/packages/primitives/src/themes/catppuccin.ts b/front/packages/primitives/src/themes/catppuccin.ts index bcb4f23c..4eddda47 100644 --- a/front/packages/primitives/src/themes/catppuccin.ts +++ b/front/packages/primitives/src/themes/catppuccin.ts @@ -24,13 +24,12 @@ import { ThemeBuilder } from "./theme"; export const catppuccin: ThemeBuilder = { light: { // Catppuccin latte - appbar: "#e64553", overlay0: "#9ca0b0", overlay1: "#7c7f93", link: "#1e66f5", default: { background: "#eff1f5", - accent: "#ea76cb", + accent: "#e64553", divider: "#8c8fa1", heading: "#4c4f69", paragraph: "#5c5f77", @@ -55,13 +54,12 @@ export const catppuccin: ThemeBuilder = { }, dark: { // Catppuccin mocha - appbar: "#89b4fa", overlay0: "#6c7086", overlay1: "#9399b2", link: "#89b4fa", default: { background: "#1e1e2e", - accent: "#f5c2e7", + accent: "#89b4fa", divider: "#7f849c", heading: "#cdd6f4", paragraph: "#bac2de", diff --git a/front/packages/primitives/src/themes/theme.tsx b/front/packages/primitives/src/themes/theme.tsx index 04663ea1..0e36463d 100644 --- a/front/packages/primitives/src/themes/theme.tsx +++ b/front/packages/primitives/src/themes/theme.tsx @@ -35,7 +35,6 @@ type FontList = Partial< >; type Mode = { - appbar: Property.Color; overlay0: Property.Color; overlay1: Property.Color; link: Property.Color; diff --git a/front/packages/ui/src/browse/grid.tsx b/front/packages/ui/src/browse/grid.tsx index b661c665..5a496def 100644 --- a/front/packages/ui/src/browse/grid.tsx +++ b/front/packages/ui/src/browse/grid.tsx @@ -66,7 +66,7 @@ export const ItemGrid = ({ fover: { self: focusReset, poster: { - borderColor: (theme) => theme.appbar, + borderColor: (theme) => theme.accent, }, title: { textDecorationLine: "underline", diff --git a/front/packages/ui/src/details/episode.tsx b/front/packages/ui/src/details/episode.tsx index 4c63a2a8..fd8651eb 100644 --- a/front/packages/ui/src/details/episode.tsx +++ b/front/packages/ui/src/details/episode.tsx @@ -22,7 +22,7 @@ import { H6, Image, Link, P, Skeleton, ts } from "@kyoo/primitives"; import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { Layout, WithLoading } from "../fetch"; -import { percent, rem, Stylable, useYoshiki } from "yoshiki/native"; +import { percent, px, rem, Stylable, Theme, useYoshiki } from "yoshiki/native"; export const episodeDisplayNumber = ( episode: { @@ -88,6 +88,21 @@ export const EpisodeLine = ({ m: ts(1), alignItems: "center", flexDirection: "row", + child: { + poster: { + borderColor: "transparent", + borderWidth: px(4), + }, + }, + focus: { + poster: { + transform: [{ scale: 1.1 }], + borderColor: (theme: Theme) => theme.accent, + }, + title: { + textDecorationLine: "underline", + }, + }, }, props, )} @@ -102,11 +117,15 @@ export const EpisodeLine = ({ width: percent(18), aspectRatio: 16 / 9, }} - {...css({ flexShrink: 0, m: ts(1) })} + {...css(["poster", { flexShrink: 0, m: ts(1) }])} /> - {isLoading ||
{name ?? t("show.episodeNoMetadata")}
} + {isLoading || ( +
+ {name ?? t("show.episodeNoMetadata")} +
+ )}
{isLoading ||

{overview}

}
diff --git a/front/packages/ui/src/details/header.tsx b/front/packages/ui/src/details/header.tsx index c5bf93f5..c2bf0dd8 100644 --- a/front/packages/ui/src/details/header.tsx +++ b/front/packages/ui/src/details/header.tsx @@ -40,7 +40,15 @@ import { } from "@kyoo/primitives"; import { Fragment } from "react"; import { useTranslation } from "react-i18next"; -import { Platform, View } from "react-native"; +import { + FlatList, + NativeSyntheticEvent, + Platform, + Pressable, + PressableProps, + TargetedEvent, + View, +} from "react-native"; import { Theme, md, @@ -160,7 +168,11 @@ const TitleLine = ({ as={Link} href={`/watch/${slug}`} color={{ xs: theme.user.colors.black, md: theme.colors.black }} - {...css({ bg: { xs: theme.user.accent, md: theme.accent } })} + hasTVPreferredFocus + {...css({ + bg: theme.user.accent, + fover: { self: { bg: theme.user.accent } }, + })} {...tooltip(t("show.play"))} /> -

theme.user.paragraph, - display: "flex", - })} - > - {t("show.studio")}:{" "} - {isLoading ? ( - - ) : ( - theme.user.link })}> - {studio!.name} - - )} -

+ {!Platform.isTV && ( +

theme.user.paragraph, + display: "flex", + })} + > + {t("show.studio")}:{" "} + {isLoading ? ( + + ) : ( + theme.user.link })}> + {studio!.name} + + )} +

+ )} ); }; +const TvPressable = ({ children, ...props }: PressableProps) => { + if (!Platform.isTV) return <>children; + return {children}; +}; + const Description = ({ isLoading, overview, @@ -225,58 +244,78 @@ const Description = ({ return ( -

theme.user.paragraph, - })} - > - {t("show.genre")}:{" "} - {(isLoading ? [...Array(3)] : genres!).map((genre, i) => ( - -

{i !== 0 && ", "}

- {isLoading ? ( - - ) : ( - {genre.name} - )} - - ))} -

+ {!Platform.isTV && ( +

theme.user.paragraph, + })} + > + {t("show.genre")}:{" "} + {(isLoading ? [...Array(3)] : genres!).map((genre, i) => ( + +

{i !== 0 && ", "}

+ {isLoading ? ( + + ) : ( + {genre.name} + )} + + ))} +

+ )} - - {isLoading || ( -

- {overview ?? t("show.noOverview")} -

- )} -
-
- -

{t("show.genre")}

- {isLoading || genres?.length ? ( -
    - {(isLoading ? [...Array(3)] : genres!).map((genre, i) => ( -
  • - {isLoading ? ( - - ) : ( - {genre.name} - )} -
  • - ))} -
- ) : ( -

{t("show.genre-none")}

- )} -
+ + + {isLoading || ( +

+ {overview ?? t("show.noOverview")} +

+ )} +
+
+ {!Platform.isTV && ( + <> +
+ +

{t("show.genre")}

+ {isLoading || genres?.length ? ( +
    + {(isLoading ? [...Array(3)] : genres!).map((genre, i) => ( +
  • + {isLoading ? ( + + ) : ( + {genre.name} + )} +
  • + ))} +
+ ) : ( +

{t("show.genre-none")}

+ )} +
+ + )}
); }; diff --git a/front/packages/ui/src/details/season.tsx b/front/packages/ui/src/details/season.tsx index c4b9565c..4d5d91bd 100644 --- a/front/packages/ui/src/details/season.tsx +++ b/front/packages/ui/src/details/season.tsx @@ -18,8 +18,8 @@ * along with Kyoo. If not, see . */ -import { Episode, EpisodeP, QueryIdentifier, Season } from "@kyoo/models"; -import { Container, SwitchVariant, ts } from "@kyoo/primitives"; +import { Episode, EpisodeP, QueryIdentifier } from "@kyoo/models"; +import { Container } from "@kyoo/primitives"; import { Stylable } from "yoshiki/native"; import { View } from "react-native"; import { InfiniteFetch } from "../fetch-infinite"; diff --git a/front/packages/ui/src/details/show.tsx b/front/packages/ui/src/details/show.tsx index 04e64f4a..7407d7d4 100644 --- a/front/packages/ui/src/details/show.tsx +++ b/front/packages/ui/src/details/show.tsx @@ -20,9 +20,9 @@ import { QueryIdentifier, QueryPage, Show, ShowP } from "@kyoo/models"; import { Platform, View, ViewProps } from "react-native"; -import { percent, useYoshiki, vh } from "yoshiki/native"; +import { percent, useYoshiki } from "yoshiki/native"; import { DefaultLayout } from "../layout"; -import { EpisodeList, SeasonTab } from "./season"; +import { EpisodeList } from "./season"; import { Header } from "./header"; import Svg, { Path, SvgProps } from "react-native-svg"; import { Container, SwitchVariant } from "@kyoo/primitives"; diff --git a/front/packages/ui/src/layout.tsx b/front/packages/ui/src/layout.tsx index 986b5e8b..3fd231e9 100644 --- a/front/packages/ui/src/layout.tsx +++ b/front/packages/ui/src/layout.tsx @@ -23,7 +23,13 @@ import { Navbar } from "./navbar"; import { useYoshiki } from "yoshiki/native"; import { Main } from "@kyoo/primitives"; -export const DefaultLayout = ({ page, transparent }: { page: ReactElement, transparent?: boolean }) => { +export const DefaultLayout = ({ + page, + transparent, +}: { + page: ReactElement; + transparent?: boolean; +}) => { const { css } = useYoshiki(); return ( @@ -36,6 +42,7 @@ export const DefaultLayout = ({ page, transparent }: { page: ReactElement, trans top: 0, left: 0, right: 0, + shadowOpacity: 0, }, )} /> diff --git a/front/packages/ui/src/navbar/index.tsx b/front/packages/ui/src/navbar/index.tsx index ebc7fcdb..4e70c1ba 100644 --- a/front/packages/ui/src/navbar/index.tsx +++ b/front/packages/ui/src/navbar/index.tsx @@ -146,7 +146,7 @@ export const Navbar = (props: Stylable) => {
theme.appbar, + backgroundColor: (theme) => theme.accent, paddingX: ts(2), height: { xs: 48, sm: 64 }, flexDirection: "row", diff --git a/front/yarn.lock b/front/yarn.lock index de3d4cb1..47351571 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -10402,7 +10402,7 @@ __metadata: react-native-svg-transformer: ^1.0.0 react-native-video: alpha typescript: ^4.6.3 - yoshiki: 1.2.0 + yoshiki: 1.2.1 languageName: unknown linkType: soft @@ -14144,7 +14144,7 @@ __metadata: superjson: ^1.11.0 typescript: ^4.9.3 webpack: ^5.75.0 - yoshiki: 1.2.0 + yoshiki: 1.2.1 zod: ^3.19.1 languageName: unknown linkType: soft @@ -14519,9 +14519,9 @@ __metadata: languageName: node linkType: hard -"yoshiki@npm:1.2.0": - version: 1.2.0 - resolution: "yoshiki@npm:1.2.0" +"yoshiki@npm:1.2.1": + version: 1.2.1 + resolution: "yoshiki@npm:1.2.1" dependencies: "@types/node": 18.x.x "@types/react": 18.x.x @@ -14536,7 +14536,7 @@ __metadata: optional: true react-native-web: optional: true - checksum: 1ef4bc33563bcf344689a5bfbdc4da1636b99552fcff041ada8fa79224c6c3fac2530a890bf6980981fb7aed9cc4e31b89feb1d0bde920179039fc573935ab42 + checksum: 8d5e58f392ca068f13bcb9b87787bf94da2e03077e733db89c40f22aaebe288536b451de2e92fba133d4241771aa87c6cc30d0bea902f94099335e162a146ab5 languageName: node linkType: hard