fix(native): enhance horizontal list components with consistent spacing

Introduce a new `horizontal-list-spacing` module to standardize spacing and separator components across various screens. Update `DashboardScreen`, `TitleDetailScreen`, and `HorizontalPosterRow` to utilize the new styles and separators, improving layout consistency. Refactor `CastCard` to use `useRouter` for navigation instead of `Link` for better performance and accessibility.
This commit is contained in:
2026-03-16 10:58:18 -04:00
parent 639fa34585
commit 88780c337e
5 changed files with 78 additions and 55 deletions
+9 -5
View File
@@ -14,14 +14,16 @@ import { ContinueWatchingCard } from "@/components/dashboard/continue-watching-c
import { HorizontalPosterRow } from "@/components/dashboard/horizontal-poster-row";
import { StatsCard } from "@/components/dashboard/stats-card";
import { EmptyState } from "@/components/ui/empty-state";
import {
HorizontalListSeparator,
horizontalListContentStyle,
horizontalListStyle,
} from "@/components/ui/horizontal-list-spacing";
import { SectionHeader } from "@/components/ui/section-header";
import { authClient } from "@/lib/auth-client";
import { orpc } from "@/lib/orpc";
import { queryClient } from "@/lib/query-client";
const horizontalListStyle = { overflow: "visible" as const };
const statsListContentStyle = { gap: 12, paddingHorizontal: 16 };
const continueWatchingContentStyle = { gap: 12, paddingHorizontal: 16 };
const dashboardContentContainerStyle = {
paddingTop: 8,
paddingBottom: 16,
@@ -99,7 +101,8 @@ export default function DashboardScreen() {
data={statsData}
keyExtractor={(item) => item.label}
renderItem={renderStatItem}
contentContainerStyle={statsListContentStyle}
ItemSeparatorComponent={HorizontalListSeparator}
contentContainerStyle={horizontalListContentStyle}
style={horizontalListStyle}
/>
</Animated.View>
@@ -116,7 +119,8 @@ export default function DashboardScreen() {
data={continueWatching.data?.items ?? []}
keyExtractor={(item) => item.title.id}
renderItem={renderContinueWatchingItem}
contentContainerStyle={continueWatchingContentStyle}
ItemSeparatorComponent={HorizontalListSeparator}
contentContainerStyle={horizontalListContentStyle}
style={horizontalListStyle}
/>
</Animated.View>
+8 -4
View File
@@ -30,6 +30,11 @@ import { ContinueWatchingBanner } from "@/components/titles/continue-watching-ba
import { SeasonAccordion } from "@/components/titles/season-accordion";
import { StatusActionButton } from "@/components/titles/status-action-button";
import { ExpandableText } from "@/components/ui/expandable-text";
import {
HorizontalListSeparator,
horizontalListContentStyle,
horizontalListStyle,
} from "@/components/ui/horizontal-list-spacing";
import { Image } from "@/components/ui/image";
import { SectionHeader } from "@/components/ui/section-header";
import { Skeleton } from "@/components/ui/skeleton";
@@ -42,8 +47,6 @@ import { queryClient } from "@/lib/query-client";
import { addRecentlyViewed } from "@/lib/recently-viewed";
import { toast } from "@/lib/toast";
const castListContentStyle = { gap: 12, paddingHorizontal: 16 };
const castListStyle = { overflow: "visible" as const };
const titleGenresContentStyle = { paddingHorizontal: 16 };
const titleAvailabilityContentStyle = { gap: 8, paddingHorizontal: 16 };
const titleDetailStyles = StyleSheet.create({
@@ -655,8 +658,9 @@ export default function TitleDetailScreen() {
data={cast}
keyExtractor={(item, index) => `${item.id}-${index}`}
renderItem={renderCastItem}
contentContainerStyle={castListContentStyle}
style={castListStyle}
ItemSeparatorComponent={HorizontalListSeparator}
contentContainerStyle={horizontalListContentStyle}
style={horizontalListStyle}
/>
</Animated.View>
)}
@@ -1,6 +1,11 @@
import { FlashList } from "@shopify/flash-list";
import { useCallback } from "react";
import {
HorizontalListSeparator,
horizontalListContentStyle,
horizontalListStyle,
} from "@/components/ui/horizontal-list-spacing";
import { PosterCard, PosterCardSkeleton } from "@/components/ui/poster-card";
import { usePosterActions } from "@/hooks/use-poster-actions";
@@ -18,9 +23,6 @@ export interface PosterRowItem {
episodeProgress?: { watched: number; total: number } | null;
}
const listContentStyle = { gap: 12, paddingHorizontal: 16 };
const listStyle = { overflow: "visible" as const };
export function HorizontalPosterRow({
items,
isLoading,
@@ -65,8 +67,9 @@ export function HorizontalPosterRow({
data={[1, 2, 3, 4]}
keyExtractor={(item) => String(item)}
renderItem={() => <PosterCardSkeleton />}
contentContainerStyle={listContentStyle}
style={listStyle}
ItemSeparatorComponent={HorizontalListSeparator}
contentContainerStyle={horizontalListContentStyle}
style={horizontalListStyle}
/>
);
}
@@ -78,8 +81,9 @@ export function HorizontalPosterRow({
data={items}
keyExtractor={keyExtractor}
renderItem={renderItem}
contentContainerStyle={listContentStyle}
style={listStyle}
ItemSeparatorComponent={HorizontalListSeparator}
contentContainerStyle={horizontalListContentStyle}
style={horizontalListStyle}
/>
);
}
+37 -39
View File
@@ -1,4 +1,4 @@
import { Link } from "expo-router";
import { useRouter } from "expo-router";
import { Pressable, View } from "react-native";
import { Image } from "@/components/ui/image";
import { Text } from "@/components/ui/text";
@@ -18,46 +18,44 @@ export function CastCard({
const accessibilityLabel = person.character
? `${person.name} as ${person.character}`
: person.name;
const { navigate } = useRouter();
return (
<Link href={`/person/${person.personId}` as `/person/${string}`}>
<Link.Trigger>
<Pressable
accessibilityRole="link"
accessibilityLabel={accessibilityLabel}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
<Pressable
accessibilityRole="link"
accessibilityLabel={accessibilityLabel}
hitSlop={8}
onPress={() => navigate(`/person/${person.personId}`)}
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
>
<View className="w-20 items-center">
<View className="mb-2 h-16 w-16 overflow-hidden rounded-full bg-secondary">
{person.profilePath && (
<Image
source={{ uri: person.profilePath }}
thumbHash={person.profileThumbHash}
recyclingKey={person.personId}
className="h-full w-full"
contentFit="cover"
accessible={false}
/>
)}
</View>
<Text
numberOfLines={1}
className="text-center font-sans-medium text-[11px] text-foreground"
>
<View className="w-20 items-center">
<View className="mb-2 h-16 w-16 overflow-hidden rounded-full bg-secondary">
{person.profilePath && (
<Image
source={{ uri: person.profilePath }}
thumbHash={person.profileThumbHash}
recyclingKey={person.personId}
className="h-full w-full"
contentFit="cover"
accessible={false}
/>
)}
</View>
<Text
numberOfLines={1}
className="text-center font-sans-medium text-[11px] text-foreground"
>
{person.name}
</Text>
{person.character ? (
<Text
numberOfLines={1}
className="text-center text-[10px] text-muted-foreground"
>
{person.character}
</Text>
) : null}
</View>
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
{person.name}
</Text>
{person.character ? (
<Text
numberOfLines={1}
className="text-center text-[10px] text-muted-foreground"
>
{person.character}
</Text>
) : null}
</View>
</Pressable>
);
}
@@ -0,0 +1,13 @@
import { memo } from "react";
import { View } from "react-native";
export const HORIZONTAL_LIST_GAP = 12;
export const HORIZONTAL_LIST_EDGE_INSET = 16;
export const horizontalListStyle = { overflow: "visible" as const };
export const horizontalListContentStyle = {
paddingHorizontal: HORIZONTAL_LIST_EDGE_INSET,
};
export const HorizontalListSeparator = memo(function HorizontalListSeparator() {
return <View style={{ width: HORIZONTAL_LIST_GAP }} />;
});