Поправлен интерфейс
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { FlatList, StyleSheet, Text, View } from "react-native";
|
||||
import { FlatList, StyleSheet, useWindowDimensions, View } from "react-native";
|
||||
import { notificationApi, notificationKeys } from "../notification-api";
|
||||
import { useNotificationAction } from "../notification-actions";
|
||||
import { typeMap } from "../notification-presenter";
|
||||
import { colors, spacing } from "../theme";
|
||||
import { layout, spacing } from "../theme";
|
||||
import type { NotificationItem } from "../types";
|
||||
import { ErrorNotice, Loading, styles } from "../ui";
|
||||
import { ErrorNotice, Loading } from "../ui";
|
||||
import { NotificationCard } from "./NotificationCard";
|
||||
|
||||
export function NotificationCarousel({
|
||||
@@ -24,6 +24,9 @@ export function NotificationCarousel({
|
||||
const list = useRef<FlatList<NotificationItem>>(null);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [actionError, setActionError] = useState<unknown>();
|
||||
const [hiddenGuestIds, setHiddenGuestIds] = useState<Set<string>>(() => new Set());
|
||||
const window = useWindowDimensions();
|
||||
const cardWidth = Math.max(0, Math.min(window.width, layout.maxWidth) - spacing.lg * 2);
|
||||
const catalog = useQuery({
|
||||
queryKey: notificationKeys.catalog,
|
||||
queryFn: notificationApi.catalog,
|
||||
@@ -45,7 +48,14 @@ export function NotificationCarousel({
|
||||
},
|
||||
onError: setActionError,
|
||||
});
|
||||
const data = notifications.data ?? [];
|
||||
const data = (notifications.data ?? []).filter((item) => authenticated || !hiddenGuestIds.has(item.id));
|
||||
|
||||
const goTo = (index: number) => {
|
||||
if (data.length < 2) return;
|
||||
const next = (index + data.length) % data.length;
|
||||
setActiveIndex(next);
|
||||
list.current?.scrollToIndex({ index: next, animated: true });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!autoplay || data.length < 2) return;
|
||||
@@ -59,6 +69,13 @@ export function NotificationCarousel({
|
||||
return () => clearInterval(timer);
|
||||
}, [autoplay, autoplayIntervalMs, data.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeIndex < data.length) return;
|
||||
const next = Math.max(data.length - 1, 0);
|
||||
setActiveIndex(next);
|
||||
list.current?.scrollToIndex({ index: next, animated: false });
|
||||
}, [activeIndex, data.length]);
|
||||
|
||||
if (notifications.isLoading || catalog.isLoading) {
|
||||
return <View style={local.state}><Loading /></View>;
|
||||
}
|
||||
@@ -76,17 +93,21 @@ export function NotificationCarousel({
|
||||
|
||||
return (
|
||||
<View style={local.section}>
|
||||
<Text accessibilityRole="header" style={[styles.heading, local.heading]}>Важное для вас</Text>
|
||||
<FlatList
|
||||
ref={list}
|
||||
horizontal
|
||||
data={data}
|
||||
decelerationRate="fast"
|
||||
pagingEnabled
|
||||
snapToInterval={cardWidth}
|
||||
snapToAlignment="start"
|
||||
getItemLayout={(_, index) => ({ length: cardWidth, offset: cardWidth * index, index })}
|
||||
keyExtractor={(item) => item.id}
|
||||
contentContainerStyle={local.content}
|
||||
ItemSeparatorComponent={() => <View style={{ width: spacing.md }} />}
|
||||
style={local.carousel}
|
||||
onMomentumScrollEnd={(event) => {
|
||||
const width = event.nativeEvent.layoutMeasurement.width;
|
||||
if (width > 0) setActiveIndex(Math.round(event.nativeEvent.contentOffset.x / width));
|
||||
if (cardWidth > 0) {
|
||||
setActiveIndex(Math.min(data.length - 1, Math.round(event.nativeEvent.contentOffset.x / cardWidth)));
|
||||
}
|
||||
}}
|
||||
renderItem={({ item }) => (
|
||||
<NotificationCard
|
||||
@@ -94,16 +115,22 @@ export function NotificationCarousel({
|
||||
item={item}
|
||||
type={byCode.get(item.notification_type)}
|
||||
onCta={() => void action(item, byCode.get(item.notification_type))}
|
||||
{...(authenticated ? { onHide: () => hide.mutate(item.id) } : {})}
|
||||
onNext={() => goTo(activeIndex + 1)}
|
||||
onPrevious={() => goTo(activeIndex - 1)}
|
||||
position={activeIndex + 1}
|
||||
total={data.length}
|
||||
width={cardWidth}
|
||||
onHide={() => {
|
||||
if (authenticated) {
|
||||
hide.mutate(item.id);
|
||||
} else {
|
||||
setHiddenGuestIds((current) => new Set(current).add(item.id));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
{data.length > 1 && (
|
||||
<View style={local.dots} accessibilityLabel={`${activeIndex + 1} из ${data.length}`}>
|
||||
{data.map((item, index) => <View key={item.id} style={[local.dot, index === activeIndex && local.dotActive]} />)}
|
||||
</View>
|
||||
)}
|
||||
{Boolean(actionError) && <View style={local.error}><ErrorNotice error={actionError} /></View>}
|
||||
</View>
|
||||
);
|
||||
@@ -111,11 +138,7 @@ export function NotificationCarousel({
|
||||
|
||||
const local = StyleSheet.create({
|
||||
section: { paddingVertical: spacing.md },
|
||||
heading: { paddingHorizontal: spacing.lg, marginBottom: spacing.sm },
|
||||
content: { paddingHorizontal: spacing.lg },
|
||||
carousel: { marginHorizontal: spacing.lg },
|
||||
state: { paddingHorizontal: spacing.lg, paddingVertical: spacing.md },
|
||||
dots: { flexDirection: "row", justifyContent: "center", gap: 6, marginTop: spacing.sm },
|
||||
dot: { width: 6, height: 6, borderRadius: 3, backgroundColor: colors.muted },
|
||||
dotActive: { width: 16, backgroundColor: colors.primary },
|
||||
error: { paddingHorizontal: spacing.lg, paddingTop: spacing.sm },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user