mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-07-14 18:15:56 -04:00
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:
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 }} />;
|
||||
});
|
||||
Reference in New Issue
Block a user