Redesign: Allow dismissing announcements panel (#40688)

This commit is contained in:
diondiondion
2026-09-24 16:28:26 +00:00
committed by GitHub
parent 07144eda65
commit 930b664da5
8 changed files with 79 additions and 36 deletions

View File

@@ -23,6 +23,8 @@ export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL = 'ANNOUNCEMENTS_REACTION_REM
export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';
export const ANNOUNCEMENTS_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';
export const ANNOUNCEMENTS_HIDE = 'ANNOUNCEMENTS_TOGGLE_SHOW';
const noOp = () => {};
@@ -175,6 +177,14 @@ export const toggleShowAnnouncements = () => ({
type: ANNOUNCEMENTS_TOGGLE_SHOW,
});
export const hideAnnouncements = () => ({
type: ANNOUNCEMENTS_HIDE,
});
export const showAnnouncements = () => ({
type: ANNOUNCEMENTS_SHOW,
});
export const deleteAnnouncement = id => ({
type: ANNOUNCEMENTS_DELETE,
id,

View File

@@ -57,6 +57,7 @@ export const Announcement: FC<AnnouncementProps> = ({
<span>
{' · '}
<Timestamp announcement={announcement} />
{!isVisuallyRead && <span className='announcements__unread' />}
</span>
</strong>
@@ -67,8 +68,6 @@ export const Announcement: FC<AnnouncementProps> = ({
/>
<ReactionsBar reactions={announcement.reactions} id={announcement.id} />
{!isVisuallyRead && <span className='announcements__unread' />}
</AnimateEmojiProvider>
);
};

View File

@@ -1,19 +1,19 @@
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import type {
List as ImmutableList,
Record as ImmutableRecord,
} from 'immutable';
import { fetchAnnouncements } from '@/mastodon/actions/announcements';
import {
fetchAnnouncements,
showAnnouncements,
} from '@/mastodon/actions/announcements';
import type { ApiAnnouncementJSON } from '@/mastodon/api_types/announcements';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { isRedesignEnabled } from '@/mastodon/utils/environment';
type AnnouncementItem = ImmutableRecord<{
id: string;
starts_at: string;
published_at: string;
read: boolean;
}>;
type AnnouncementItem = ImmutableRecord<ApiAnnouncementJSON>;
type AnnouncementsState = ImmutableRecord<{
items: ImmutableList<AnnouncementItem>;
@@ -39,17 +39,11 @@ export function useHasAnnouncements({
);
const hasUnreadAnnouncements = !!unreadAnnouncementCount;
// Announcements display is transient – it's toggled on when
// there are unread announcements or the `show` Redux state is
// enabled, but resets to hidden when the page is left (by virtue
// of React simply losing this state).
const [showAnnouncements, setShowAnnouncements] = useState(false);
if (
(hasUnreadAnnouncements || shouldShowAnnouncements) &&
!showAnnouncements
) {
setShowAnnouncements(true);
}
useEffect(() => {
if (hasUnreadAnnouncements && isRedesignEnabled()) {
dispatch(showAnnouncements());
}
}, [hasUnreadAnnouncements, dispatch]);
useEffect(() => {
const timeout = setTimeout(() => {
@@ -66,7 +60,7 @@ export function useHasAnnouncements({
return {
hasAnnouncements,
hasUnreadAnnouncements,
showAnnouncements,
shouldShowAnnouncements,
unreadAnnouncementCount,
};
}

View File

@@ -1,15 +1,26 @@
import { useCallback } from 'react';
import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import type { Map, List } from 'immutable';
import { XIcon } from '@phosphor-icons/react';
import elephantUIPlane from '@/images/elephant_ui_plane.svg';
import { hideAnnouncements } from '@/mastodon/actions/announcements';
import { IconButton } from '@/mastodon/components/button/redesign';
import type { RenderSlideFn } from '@/mastodon/components/carousel';
import { Carousel } from '@/mastodon/components/carousel';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import { useCustomEmojis } from '@/mastodon/hooks/useCustomEmojis';
import { mascot } from '@/mastodon/initial_state';
import { createAppSelector, useAppSelector } from '@/mastodon/store';
import {
createAppSelector,
useAppDispatch,
useAppSelector,
} from '@/mastodon/store';
import { isRedesignEnabled } from '@/mastodon/utils/environment';
import type { IAnnouncement } from './announcement';
import { Announcement } from './announcement';
@@ -23,9 +34,14 @@ const announcementSelector = createAppSelector(
);
export const Announcements: FC = () => {
const dispatch = useAppDispatch();
const announcements = useAppSelector(announcementSelector);
const emojis = useCustomEmojis();
const closeAnnouncements = useCallback(() => {
dispatch(hideAnnouncements());
}, [dispatch]);
const renderSlide: RenderSlideFn<{
id: string;
announcement: IAnnouncement;
@@ -53,6 +69,18 @@ export const Announcements: FC = () => {
src={mascot ?? elephantUIPlane}
/>
{isRedesignEnabled() && (
<IconButton
icon={XIcon}
onClick={closeAnnouncements}
size='sm'
variant='ghost'
className='announcements__close-button'
>
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
</IconButton>
)}
<CustomEmojiProvider emojis={emojis}>
<Carousel
classNamePrefix='announcements'

View File

@@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
import { NewspaperIcon } from '@phosphor-icons/react';
import { toggleShowAnnouncements } from '@/mastodon/actions/announcements';
import { showAnnouncements } from '@/mastodon/actions/announcements';
import { closeModal } from '@/mastodon/actions/modal';
import { Button } from '@/mastodon/components/button/redesign';
import { useAppDispatch } from '@/mastodon/store';
@@ -14,18 +14,18 @@ import { useHasAnnouncements } from '../../announcements/hooks';
export const ShowAnnouncementsButton: React.FC = () => {
const dispatch = useAppDispatch();
const { hasAnnouncements, showAnnouncements } = useHasAnnouncements({
const { hasAnnouncements, shouldShowAnnouncements } = useHasAnnouncements({
fetch: false,
});
const handleClick = useCallback(() => {
dispatch(toggleShowAnnouncements());
dispatch(showAnnouncements());
dispatch(
closeModal({ modalType: 'NOTIFICATION_SETTINGS', ignoreFocus: false }),
);
}, [dispatch]);
if (!isRedesignEnabled() || !hasAnnouncements || showAnnouncements) {
if (!isRedesignEnabled() || !hasAnnouncements || shouldShowAnnouncements) {
return null;
}

View File

@@ -222,12 +222,12 @@ export const Notifications: React.FC<{
);
}, [notifications, isLoading, hasMore, lastReadId, handleLoadGap]);
const { showAnnouncements } = useHasAnnouncements();
const { shouldShowAnnouncements } = useHasAnnouncements();
const prepend = (
<>
{needsNotificationPermission && <NotificationsPermissionBanner />}
{showAnnouncements && <Announcements />}
{shouldShowAnnouncements && <Announcements />}
<FilteredNotificationsBanner />
</>
);

View File

@@ -13,6 +13,8 @@ import {
ANNOUNCEMENTS_TOGGLE_SHOW,
ANNOUNCEMENTS_DELETE,
ANNOUNCEMENTS_DISMISS_SUCCESS,
ANNOUNCEMENTS_SHOW,
ANNOUNCEMENTS_HIDE,
} from '../actions/announcements';
const initialState = ImmutableMap({
@@ -64,6 +66,10 @@ export default function announcementsReducer(state = initialState, action) {
return state.withMutations(map => {
map.set('show', !map.get('show'));
});
case ANNOUNCEMENTS_SHOW:
return state.set('show', true);
case ANNOUNCEMENTS_HIDE:
return state.set('show', false);
case ANNOUNCEMENTS_FETCH_REQUEST:
return state.set('isLoading', true);
case ANNOUNCEMENTS_FETCH_SUCCESS:

View File

@@ -8543,6 +8543,13 @@ noscript {
width: 100%;
}
&__close-button {
position: absolute;
top: var(--space-2);
inset-inline-end: var(--space-2);
z-index: 1;
}
&__root {
background: var(--color-bg-brand-softest);
font-size: 13px;
@@ -8584,18 +8591,17 @@ noscript {
display: block;
font-weight: 500;
margin-bottom: 10px;
padding-inline-end: 18px;
padding-inline-end: 28px;
}
&__unread {
position: absolute;
top: 19px;
inset-inline-end: 19px;
display: block;
background: var(--color-text-brand);
border-radius: 50%;
display: inline-block;
width: 0.625rem;
height: 0.625rem;
margin-inline-start: 8px;
border-radius: 50%;
vertical-align: 0.05em;
background: var(--color-text-brand);
}
&__pagination {