pl-fe: refactor to using reusable columns
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
289
packages/pl-fe/src/columns/notifications.tsx
Normal file
289
packages/pl-fe/src/columns/notifications.tsx
Normal file
@ -0,0 +1,289 @@
|
||||
import clsx from 'clsx';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import {
|
||||
type FilterType,
|
||||
expandNotifications,
|
||||
markReadNotifications,
|
||||
scrollTopNotifications,
|
||||
setFilter,
|
||||
} from 'pl-fe/actions/notifications';
|
||||
import PullToRefresh from 'pl-fe/components/pull-to-refresh';
|
||||
import ScrollableList from 'pl-fe/components/scrollable-list';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Tabs from 'pl-fe/components/ui/tabs';
|
||||
import Notification from 'pl-fe/features/notifications/components/notification';
|
||||
import PlaceholderNotification from 'pl-fe/features/placeholder/components/placeholder-notification';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
|
||||
import type { Item } from 'pl-fe/components/ui/tabs';
|
||||
import type { RootState } from 'pl-fe/store';
|
||||
import type { VirtuosoHandle } from 'react-virtuoso';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||
queue: { id: 'notifications.queue_label', defaultMessage: 'Click to see {count} new {count, plural, one {notification} other {notifications}}' },
|
||||
all: { id: 'notifications.filter.all', defaultMessage: 'All' },
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Likes' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
events: { id: 'notifications.filter.events', defaultMessage: 'Events' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
});
|
||||
|
||||
const FilterBar = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const settings = useSettings();
|
||||
const features = useFeatures();
|
||||
|
||||
const selectedFilter = settings.notifications.quickFilter.active;
|
||||
const advancedMode = settings.notifications.quickFilter.advanced;
|
||||
|
||||
const onClick = (notificationType: FilterType) => () => {
|
||||
try {
|
||||
dispatch(setFilter(notificationType, true));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const items: Item[] = [
|
||||
{
|
||||
text: intl.formatMessage(messages.all),
|
||||
action: onClick('all'),
|
||||
name: 'all',
|
||||
},
|
||||
];
|
||||
|
||||
if (!advancedMode) {
|
||||
items.push({
|
||||
text: intl.formatMessage(messages.mentions),
|
||||
action: onClick('mention'),
|
||||
name: 'mention',
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/at.svg')} />,
|
||||
title: intl.formatMessage(messages.mentions),
|
||||
action: onClick('mention'),
|
||||
name: 'mention',
|
||||
});
|
||||
if (features.accountNotifies) items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/bell-ringing.svg')} />,
|
||||
title: intl.formatMessage(messages.statuses),
|
||||
action: onClick('status'),
|
||||
name: 'status',
|
||||
});
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/star.svg')} />,
|
||||
title: intl.formatMessage(messages.favourites),
|
||||
action: onClick('favourite'),
|
||||
name: 'favourite',
|
||||
});
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/repeat.svg')} />,
|
||||
title: intl.formatMessage(messages.boosts),
|
||||
action: onClick('reblog'),
|
||||
name: 'reblog',
|
||||
});
|
||||
if (features.polls) items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/chart-bar.svg')} />,
|
||||
title: intl.formatMessage(messages.polls),
|
||||
action: onClick('poll'),
|
||||
name: 'poll',
|
||||
});
|
||||
if (features.events) items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/calendar.svg')} />,
|
||||
title: intl.formatMessage(messages.events),
|
||||
action: onClick('events'),
|
||||
name: 'events',
|
||||
});
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/user-plus.svg')} />,
|
||||
title: intl.formatMessage(messages.follows),
|
||||
action: onClick('follow'),
|
||||
name: 'follow',
|
||||
});
|
||||
}
|
||||
|
||||
return <Tabs items={items} activeItem={selectedFilter} />;
|
||||
};
|
||||
|
||||
const getNotifications = createSelector([
|
||||
(state: RootState) => state.notifications.items,
|
||||
(_, topNotification?: string) => topNotification,
|
||||
], (notifications, topNotificationId) => {
|
||||
if (topNotificationId) {
|
||||
const queuedNotificationCount = notifications.findIndex((notification) =>
|
||||
notification.most_recent_notification_id <= topNotificationId,
|
||||
);
|
||||
const displayedNotifications = notifications.slice(queuedNotificationCount);
|
||||
|
||||
return {
|
||||
queuedNotificationCount,
|
||||
displayedNotifications,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
queuedNotificationCount: 0,
|
||||
displayedNotifications: notifications,
|
||||
};
|
||||
});
|
||||
|
||||
const NotificationsColumn = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
const settings = useSettings();
|
||||
|
||||
const showFilterBar = (features.notificationsExcludeTypes || features.notificationsIncludeTypes) && settings.notifications.quickFilter.show;
|
||||
const activeFilter = settings.notifications.quickFilter.active;
|
||||
const [topNotification, setTopNotification] = useState<string>();
|
||||
const { displayedNotifications } = useAppSelector(state => getNotifications(state, topNotification));
|
||||
const isLoading = useAppSelector(state => state.notifications.isLoading);
|
||||
// const isUnread = useAppSelector(state => state.notifications.unread > 0);
|
||||
const hasMore = useAppSelector(state => state.notifications.hasMore);
|
||||
|
||||
const node = useRef<VirtuosoHandle>(null);
|
||||
const scrollableContentRef = useRef<Array<JSX.Element> | null>(null);
|
||||
|
||||
// const handleLoadGap = (maxId) => {
|
||||
// dispatch(expandNotifications({ maxId }));
|
||||
// };
|
||||
|
||||
const handleLoadOlder = useCallback(debounce(() => {
|
||||
const minId = displayedNotifications.reduce<string | undefined>(
|
||||
(minId, notification) => minId && notification.page_min_id && notification.page_min_id > minId
|
||||
? minId
|
||||
: notification.page_min_id,
|
||||
undefined,
|
||||
);
|
||||
dispatch(expandNotifications({ maxId: minId }));
|
||||
}, 300, { leading: true }), [displayedNotifications]);
|
||||
|
||||
const handleScrollToTop = useCallback(debounce(() => {
|
||||
dispatch(scrollTopNotifications(true));
|
||||
}, 100), []);
|
||||
|
||||
const handleScroll = useCallback(debounce(() => {
|
||||
dispatch(scrollTopNotifications(false));
|
||||
}, 100), []);
|
||||
|
||||
const handleMoveUp = (id: string) => {
|
||||
const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) - 1;
|
||||
_selectChild(elementIndex);
|
||||
};
|
||||
|
||||
const handleMoveDown = (id: string) => {
|
||||
const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) + 1;
|
||||
_selectChild(elementIndex);
|
||||
};
|
||||
|
||||
const _selectChild = (index: number) => {
|
||||
const selector = `[data-index="${index}"] .focusable`;
|
||||
const element = document.querySelector<HTMLDivElement>(selector);
|
||||
|
||||
if (element) element.focus();
|
||||
|
||||
node.current?.scrollIntoView({
|
||||
index,
|
||||
behavior: 'smooth',
|
||||
done: () => {
|
||||
if (!element) document.querySelector<HTMLDivElement>(selector)?.focus();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDequeueNotifications = useCallback(() => {
|
||||
setTopNotification(undefined);
|
||||
dispatch(markReadNotifications());
|
||||
}, []);
|
||||
|
||||
const handleRefresh = useCallback(() => dispatch(expandNotifications()), []);
|
||||
|
||||
useEffect(() => {
|
||||
handleDequeueNotifications();
|
||||
dispatch(scrollTopNotifications(true));
|
||||
|
||||
return () => {
|
||||
handleLoadOlder.cancel?.();
|
||||
handleScrollToTop.cancel();
|
||||
handleScroll.cancel?.();
|
||||
dispatch(scrollTopNotifications(false));
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (topNotification || displayedNotifications.length === 0) return;
|
||||
setTopNotification(displayedNotifications[0].most_recent_notification_id);
|
||||
}, [displayedNotifications.length]);
|
||||
|
||||
const emptyMessage = activeFilter === 'all'
|
||||
? <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
|
||||
: <FormattedMessage id='empty_column.notifications_filtered' defaultMessage="You don't have any notifications of this type yet." />;
|
||||
|
||||
let scrollableContent: Array<JSX.Element> | null = null;
|
||||
|
||||
const filterBarContainer = showFilterBar
|
||||
? (<FilterBar />)
|
||||
: null;
|
||||
|
||||
if (isLoading && scrollableContentRef.current) {
|
||||
scrollableContent = scrollableContentRef.current;
|
||||
} else if (displayedNotifications.length > 0 || hasMore) {
|
||||
scrollableContent = displayedNotifications.map((item) => (
|
||||
<Notification
|
||||
key={item.group_key}
|
||||
notification={item}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
/>
|
||||
));
|
||||
} else {
|
||||
scrollableContent = null;
|
||||
}
|
||||
|
||||
scrollableContentRef.current = scrollableContent;
|
||||
|
||||
const scrollContainer = (
|
||||
<ScrollableList
|
||||
ref={node}
|
||||
scrollKey='notifications'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && displayedNotifications.length === 0}
|
||||
hasMore={hasMore}
|
||||
emptyMessage={emptyMessage}
|
||||
placeholderComponent={PlaceholderNotification}
|
||||
placeholderCount={20}
|
||||
onLoadMore={handleLoadOlder}
|
||||
onScrollToTop={handleScrollToTop}
|
||||
onScroll={handleScroll}
|
||||
listClassName={clsx('divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800', {
|
||||
'animate-pulse': displayedNotifications.length === 0,
|
||||
})}
|
||||
>
|
||||
{scrollableContent!}
|
||||
</ScrollableList>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filterBarContainer}
|
||||
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
{scrollContainer}
|
||||
</PullToRefresh>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotificationsColumn as default };
|
||||
@ -1,126 +1,24 @@
|
||||
import clsx from 'clsx';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import {
|
||||
type FilterType,
|
||||
expandNotifications,
|
||||
markReadNotifications,
|
||||
scrollTopNotifications,
|
||||
setFilter,
|
||||
} from 'pl-fe/actions/notifications';
|
||||
import PullToRefresh from 'pl-fe/components/pull-to-refresh';
|
||||
import { markReadNotifications } from 'pl-fe/actions/notifications';
|
||||
import NotificationsColumn from 'pl-fe/columns/notifications';
|
||||
import ScrollTopButton from 'pl-fe/components/scroll-top-button';
|
||||
import ScrollableList from 'pl-fe/components/scrollable-list';
|
||||
import Column from 'pl-fe/components/ui/column';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Portal from 'pl-fe/components/ui/portal';
|
||||
import Tabs from 'pl-fe/components/ui/tabs';
|
||||
import Notification from 'pl-fe/features/notifications/components/notification';
|
||||
import PlaceholderNotification from 'pl-fe/features/placeholder/components/placeholder-notification';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
|
||||
import type { Item } from 'pl-fe/components/ui/tabs';
|
||||
import type { RootState } from 'pl-fe/store';
|
||||
import type { VirtuosoHandle } from 'react-virtuoso';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||
queue: { id: 'notifications.queue_label', defaultMessage: 'Click to see {count} new {count, plural, one {notification} other {notifications}}' },
|
||||
all: { id: 'notifications.filter.all', defaultMessage: 'All' },
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Likes' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
events: { id: 'notifications.filter.events', defaultMessage: 'Events' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
});
|
||||
|
||||
const FilterBar = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const settings = useSettings();
|
||||
const features = useFeatures();
|
||||
|
||||
const selectedFilter = settings.notifications.quickFilter.active;
|
||||
const advancedMode = settings.notifications.quickFilter.advanced;
|
||||
|
||||
const onClick = (notificationType: FilterType) => () => {
|
||||
try {
|
||||
dispatch(setFilter(notificationType, true));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const items: Item[] = [
|
||||
{
|
||||
text: intl.formatMessage(messages.all),
|
||||
action: onClick('all'),
|
||||
name: 'all',
|
||||
},
|
||||
];
|
||||
|
||||
if (!advancedMode) {
|
||||
items.push({
|
||||
text: intl.formatMessage(messages.mentions),
|
||||
action: onClick('mention'),
|
||||
name: 'mention',
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/at.svg')} />,
|
||||
title: intl.formatMessage(messages.mentions),
|
||||
action: onClick('mention'),
|
||||
name: 'mention',
|
||||
});
|
||||
if (features.accountNotifies) items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/bell-ringing.svg')} />,
|
||||
title: intl.formatMessage(messages.statuses),
|
||||
action: onClick('status'),
|
||||
name: 'status',
|
||||
});
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/star.svg')} />,
|
||||
title: intl.formatMessage(messages.favourites),
|
||||
action: onClick('favourite'),
|
||||
name: 'favourite',
|
||||
});
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/repeat.svg')} />,
|
||||
title: intl.formatMessage(messages.boosts),
|
||||
action: onClick('reblog'),
|
||||
name: 'reblog',
|
||||
});
|
||||
if (features.polls) items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/chart-bar.svg')} />,
|
||||
title: intl.formatMessage(messages.polls),
|
||||
action: onClick('poll'),
|
||||
name: 'poll',
|
||||
});
|
||||
if (features.events) items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/calendar.svg')} />,
|
||||
title: intl.formatMessage(messages.events),
|
||||
action: onClick('events'),
|
||||
name: 'events',
|
||||
});
|
||||
items.push({
|
||||
text: <Icon className='size-4' src={require('@tabler/icons/outline/user-plus.svg')} />,
|
||||
title: intl.formatMessage(messages.follows),
|
||||
action: onClick('follow'),
|
||||
name: 'follow',
|
||||
});
|
||||
}
|
||||
|
||||
return <Tabs items={items} activeItem={selectedFilter} />;
|
||||
};
|
||||
|
||||
const getNotifications = createSelector([
|
||||
(state: RootState) => state.notifications.items,
|
||||
(_, topNotification?: string) => topNotification,
|
||||
@ -150,139 +48,21 @@ const NotificationsPage = () => {
|
||||
const settings = useSettings();
|
||||
|
||||
const showFilterBar = (features.notificationsExcludeTypes || features.notificationsIncludeTypes) && settings.notifications.quickFilter.show;
|
||||
const activeFilter = settings.notifications.quickFilter.active;
|
||||
const [topNotification, setTopNotification] = useState<string>();
|
||||
const { queuedNotificationCount, displayedNotifications } = useAppSelector(state => getNotifications(state, topNotification));
|
||||
const isLoading = useAppSelector(state => state.notifications.isLoading);
|
||||
// const isUnread = useAppSelector(state => state.notifications.unread > 0);
|
||||
const hasMore = useAppSelector(state => state.notifications.hasMore);
|
||||
|
||||
const node = useRef<VirtuosoHandle>(null);
|
||||
const scrollableContentRef = useRef<Array<JSX.Element> | null>(null);
|
||||
|
||||
// const handleLoadGap = (maxId) => {
|
||||
// dispatch(expandNotifications({ maxId }));
|
||||
// };
|
||||
|
||||
const handleLoadOlder = useCallback(debounce(() => {
|
||||
const minId = displayedNotifications.reduce<string | undefined>(
|
||||
(minId, notification) => minId && notification.page_min_id && notification.page_min_id > minId
|
||||
? minId
|
||||
: notification.page_min_id,
|
||||
undefined,
|
||||
);
|
||||
dispatch(expandNotifications({ maxId: minId }));
|
||||
}, 300, { leading: true }), [displayedNotifications]);
|
||||
|
||||
const handleScrollToTop = useCallback(debounce(() => {
|
||||
dispatch(scrollTopNotifications(true));
|
||||
}, 100), []);
|
||||
|
||||
const handleScroll = useCallback(debounce(() => {
|
||||
dispatch(scrollTopNotifications(false));
|
||||
}, 100), []);
|
||||
|
||||
const handleMoveUp = (id: string) => {
|
||||
const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) - 1;
|
||||
_selectChild(elementIndex);
|
||||
};
|
||||
|
||||
const handleMoveDown = (id: string) => {
|
||||
const elementIndex = displayedNotifications.findIndex(item => item !== null && item.group_key === id) + 1;
|
||||
_selectChild(elementIndex);
|
||||
};
|
||||
|
||||
const _selectChild = (index: number) => {
|
||||
const selector = `[data-index="${index}"] .focusable`;
|
||||
const element = document.querySelector<HTMLDivElement>(selector);
|
||||
|
||||
if (element) element.focus();
|
||||
|
||||
node.current?.scrollIntoView({
|
||||
index,
|
||||
behavior: 'smooth',
|
||||
done: () => {
|
||||
if (!element) document.querySelector<HTMLDivElement>(selector)?.focus();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDequeueNotifications = useCallback(() => {
|
||||
setTopNotification(undefined);
|
||||
dispatch(markReadNotifications());
|
||||
}, []);
|
||||
|
||||
const handleRefresh = useCallback(() => dispatch(expandNotifications()), []);
|
||||
|
||||
useEffect(() => {
|
||||
handleDequeueNotifications();
|
||||
dispatch(scrollTopNotifications(true));
|
||||
|
||||
return () => {
|
||||
handleLoadOlder.cancel?.();
|
||||
handleScrollToTop.cancel();
|
||||
handleScroll.cancel?.();
|
||||
dispatch(scrollTopNotifications(false));
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (topNotification || displayedNotifications.length === 0) return;
|
||||
setTopNotification(displayedNotifications[0].most_recent_notification_id);
|
||||
}, [displayedNotifications.length]);
|
||||
|
||||
const emptyMessage = activeFilter === 'all'
|
||||
? <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
|
||||
: <FormattedMessage id='empty_column.notifications_filtered' defaultMessage="You don't have any notifications of this type yet." />;
|
||||
|
||||
let scrollableContent: Array<JSX.Element> | null = null;
|
||||
|
||||
const filterBarContainer = showFilterBar
|
||||
? (<FilterBar />)
|
||||
: null;
|
||||
|
||||
if (isLoading && scrollableContentRef.current) {
|
||||
scrollableContent = scrollableContentRef.current;
|
||||
} else if (displayedNotifications.length > 0 || hasMore) {
|
||||
scrollableContent = displayedNotifications.map((item) => (
|
||||
<Notification
|
||||
key={item.group_key}
|
||||
notification={item}
|
||||
onMoveUp={handleMoveUp}
|
||||
onMoveDown={handleMoveDown}
|
||||
/>
|
||||
));
|
||||
} else {
|
||||
scrollableContent = null;
|
||||
}
|
||||
|
||||
scrollableContentRef.current = scrollableContent;
|
||||
|
||||
const scrollContainer = (
|
||||
<ScrollableList
|
||||
ref={node}
|
||||
scrollKey='notifications'
|
||||
isLoading={isLoading}
|
||||
showLoading={isLoading && displayedNotifications.length === 0}
|
||||
hasMore={hasMore}
|
||||
emptyMessage={emptyMessage}
|
||||
placeholderComponent={PlaceholderNotification}
|
||||
placeholderCount={20}
|
||||
onLoadMore={handleLoadOlder}
|
||||
onScrollToTop={handleScrollToTop}
|
||||
onScroll={handleScroll}
|
||||
listClassName={clsx('divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800', {
|
||||
'animate-pulse': displayedNotifications.length === 0,
|
||||
})}
|
||||
>
|
||||
{scrollableContent!}
|
||||
</ScrollableList>
|
||||
);
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)} withHeader={!showFilterBar}>
|
||||
{filterBarContainer}
|
||||
|
||||
<Portal>
|
||||
<ScrollTopButton
|
||||
onClick={handleDequeueNotifications}
|
||||
@ -291,9 +71,7 @@ const NotificationsPage = () => {
|
||||
/>
|
||||
</Portal>
|
||||
|
||||
<PullToRefresh onRefresh={handleRefresh}>
|
||||
{scrollContainer}
|
||||
</PullToRefresh>
|
||||
<NotificationsColumn />
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user