diff --git a/packages/pl-fe/src/actions/profile-hover-card.ts b/packages/pl-fe/src/actions/profile-hover-card.ts deleted file mode 100644 index 3675d7517..000000000 --- a/packages/pl-fe/src/actions/profile-hover-card.ts +++ /dev/null @@ -1,27 +0,0 @@ -const PROFILE_HOVER_CARD_OPEN = 'PROFILE_HOVER_CARD_OPEN'; -const PROFILE_HOVER_CARD_UPDATE = 'PROFILE_HOVER_CARD_UPDATE'; -const PROFILE_HOVER_CARD_CLOSE = 'PROFILE_HOVER_CARD_CLOSE'; - -const openProfileHoverCard = (ref: React.MutableRefObject, accountId: string) => ({ - type: PROFILE_HOVER_CARD_OPEN, - ref, - accountId, -}); - -const updateProfileHoverCard = () => ({ - type: PROFILE_HOVER_CARD_UPDATE, -}); - -const closeProfileHoverCard = (force = false) => ({ - type: PROFILE_HOVER_CARD_CLOSE, - force, -}); - -export { - PROFILE_HOVER_CARD_OPEN, - PROFILE_HOVER_CARD_UPDATE, - PROFILE_HOVER_CARD_CLOSE, - openProfileHoverCard, - updateProfileHoverCard, - closeProfileHoverCard, -}; diff --git a/packages/pl-fe/src/actions/sidebar.ts b/packages/pl-fe/src/actions/sidebar.ts deleted file mode 100644 index 6d5110220..000000000 --- a/packages/pl-fe/src/actions/sidebar.ts +++ /dev/null @@ -1,17 +0,0 @@ -const SIDEBAR_OPEN = 'SIDEBAR_OPEN'; -const SIDEBAR_CLOSE = 'SIDEBAR_CLOSE'; - -const openSidebar = () => ({ - type: SIDEBAR_OPEN, -}); - -const closeSidebar = () => ({ - type: SIDEBAR_CLOSE, -}); - -export { - SIDEBAR_OPEN, - SIDEBAR_CLOSE, - openSidebar, - closeSidebar, -}; diff --git a/packages/pl-fe/src/actions/status-hover-card.ts b/packages/pl-fe/src/actions/status-hover-card.ts deleted file mode 100644 index 2ce24a745..000000000 --- a/packages/pl-fe/src/actions/status-hover-card.ts +++ /dev/null @@ -1,27 +0,0 @@ -const STATUS_HOVER_CARD_OPEN = 'STATUS_HOVER_CARD_OPEN'; -const STATUS_HOVER_CARD_UPDATE = 'STATUS_HOVER_CARD_UPDATE'; -const STATUS_HOVER_CARD_CLOSE = 'STATUS_HOVER_CARD_CLOSE'; - -const openStatusHoverCard = (ref: React.MutableRefObject, statusId: string) => ({ - type: STATUS_HOVER_CARD_OPEN, - ref, - statusId, -}); - -const updateStatusHoverCard = () => ({ - type: STATUS_HOVER_CARD_UPDATE, -}); - -const closeStatusHoverCard = (force = false) => ({ - type: STATUS_HOVER_CARD_CLOSE, - force, -}); - -export { - STATUS_HOVER_CARD_OPEN, - STATUS_HOVER_CARD_UPDATE, - STATUS_HOVER_CARD_CLOSE, - openStatusHoverCard, - updateStatusHoverCard, - closeStatusHoverCard, -}; diff --git a/packages/pl-fe/src/components/dropdown-menu/dropdown-menu.tsx b/packages/pl-fe/src/components/dropdown-menu/dropdown-menu.tsx index 8e348994e..4247287f3 100644 --- a/packages/pl-fe/src/components/dropdown-menu/dropdown-menu.tsx +++ b/packages/pl-fe/src/components/dropdown-menu/dropdown-menu.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import ReactSwipeableViews from 'react-swipeable-views'; import { userTouching } from 'pl-fe/is-mobile'; -import { useDropdownMenuStore, useModalsStore } from 'pl-fe/stores'; +import { useUiStore, useModalsStore } from 'pl-fe/stores'; import { HStack, IconButton, Portal } from '../ui'; @@ -187,7 +187,7 @@ const DropdownMenu = (props: IDropdownMenu) => { title = 'Menu', } = props; - const { openDropdownMenu, closeDropdownMenu } = useDropdownMenuStore(); + const { openDropdownMenu, closeDropdownMenu } = useUiStore(); const { openModal, closeModal } = useModalsStore(); const [isOpen, setIsOpen] = useState(false); diff --git a/packages/pl-fe/src/components/hover-ref-wrapper.tsx b/packages/pl-fe/src/components/hover-ref-wrapper.tsx index 351694b9a..e4da5493a 100644 --- a/packages/pl-fe/src/components/hover-ref-wrapper.tsx +++ b/packages/pl-fe/src/components/hover-ref-wrapper.tsx @@ -3,12 +3,12 @@ import debounce from 'lodash/debounce'; import React, { useRef } from 'react'; import { fetchAccount } from 'pl-fe/actions/accounts'; -import { openProfileHoverCard, closeProfileHoverCard } from 'pl-fe/actions/profile-hover-card'; import { useAppDispatch } from 'pl-fe/hooks'; import { isMobile } from 'pl-fe/is-mobile'; +import { useAccountHoverCardStore } from 'pl-fe/stores'; -const showProfileHoverCard = debounce((dispatch, ref, accountId) => { - dispatch(openProfileHoverCard(ref, accountId)); +const showProfileHoverCard = debounce((openAccountHoverCard, ref, accountId) => { + openAccountHoverCard(ref, accountId); }, 600); interface IHoverRefWrapper { @@ -21,24 +21,27 @@ interface IHoverRefWrapper { /** Makes a profile hover card appear when the wrapped element is hovered. */ const HoverRefWrapper: React.FC = ({ accountId, children, inline = false, className }) => { const dispatch = useAppDispatch(); + + const { openAccountHoverCard, closeAccountHoverCard } = useAccountHoverCardStore(); + const ref = useRef(null); const Elem: keyof JSX.IntrinsicElements = inline ? 'span' : 'div'; const handleMouseEnter = () => { if (!isMobile(window.innerWidth)) { dispatch(fetchAccount(accountId)); - showProfileHoverCard(dispatch, ref, accountId); + showProfileHoverCard(openAccountHoverCard, ref, accountId); } }; const handleMouseLeave = () => { showProfileHoverCard.cancel(); - setTimeout(() => dispatch(closeProfileHoverCard()), 300); + setTimeout(() => closeAccountHoverCard(), 300); }; const handleClick = () => { showProfileHoverCard.cancel(); - dispatch(closeProfileHoverCard(true)); + closeAccountHoverCard(true); }; return ( diff --git a/packages/pl-fe/src/components/hover-status-wrapper.tsx b/packages/pl-fe/src/components/hover-status-wrapper.tsx index f27cba03c..b7dc7235d 100644 --- a/packages/pl-fe/src/components/hover-status-wrapper.tsx +++ b/packages/pl-fe/src/components/hover-status-wrapper.tsx @@ -1,13 +1,12 @@ import clsx from 'clsx'; import debounce from 'lodash/debounce'; import React, { useRef } from 'react'; -import { useDispatch } from 'react-redux'; -import { openStatusHoverCard, closeStatusHoverCard } from 'pl-fe/actions/status-hover-card'; import { isMobile } from 'pl-fe/is-mobile'; +import { useStatusHoverCardStore } from 'pl-fe/stores'; -const showStatusHoverCard = debounce((dispatch, ref, statusId) => { - dispatch(openStatusHoverCard(ref, statusId)); +const showStatusHoverCard = debounce((openStatusHoverCard, ref, statusId) => { + openStatusHoverCard(ref, statusId); }, 300); interface IHoverStatusWrapper { @@ -19,24 +18,25 @@ interface IHoverStatusWrapper { /** Makes a status hover card appear when the wrapped element is hovered. */ const HoverStatusWrapper: React.FC = ({ statusId, children, inline = false, className }) => { - const dispatch = useDispatch(); + const { openStatusHoverCard, closeStatusHoverCard } = useStatusHoverCardStore(); + const ref = useRef(null); const Elem: keyof JSX.IntrinsicElements = inline ? 'span' : 'div'; const handleMouseEnter = () => { if (!isMobile(window.innerWidth)) { - showStatusHoverCard(dispatch, ref, statusId); + showStatusHoverCard(openStatusHoverCard, ref, statusId); } }; const handleMouseLeave = () => { showStatusHoverCard.cancel(); - setTimeout(() => dispatch(closeStatusHoverCard()), 200); + setTimeout(() => closeStatusHoverCard(), 200); }; const handleClick = () => { showStatusHoverCard.cancel(); - dispatch(closeStatusHoverCard(true)); + closeStatusHoverCard(true); }; return ( diff --git a/packages/pl-fe/src/components/profile-hover-card.tsx b/packages/pl-fe/src/components/profile-hover-card.tsx index 7434a4455..36637bcc2 100644 --- a/packages/pl-fe/src/components/profile-hover-card.tsx +++ b/packages/pl-fe/src/components/profile-hover-card.tsx @@ -5,12 +5,12 @@ import { useIntl, FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { fetchRelationships } from 'pl-fe/actions/accounts'; -import { closeProfileHoverCard, updateProfileHoverCard } from 'pl-fe/actions/profile-hover-card'; import { useAccount } from 'pl-fe/api/hooks'; import Badge from 'pl-fe/components/badge'; import ActionButton from 'pl-fe/features/ui/components/action-button'; import { UserPanel } from 'pl-fe/features/ui/util/async-components'; import { useAppSelector, useAppDispatch } from 'pl-fe/hooks'; +import { useAccountHoverCardStore } from 'pl-fe/stores'; import { showProfileHoverCard } from './hover-ref-wrapper'; import { dateFormatOptions } from './relative-timestamp'; @@ -18,7 +18,6 @@ import Scrobble from './scrobble'; import { Card, CardBody, HStack, Icon, Stack, Text } from './ui'; import type { Account } from 'pl-fe/normalizers'; -import type { AppDispatch } from 'pl-fe/store'; const getBadges = ( account?: Pick, @@ -34,14 +33,6 @@ const getBadges = ( return badges; }; -const handleMouseEnter = (dispatch: AppDispatch): React.MouseEventHandler => () => { - dispatch(updateProfileHoverCard()); -}; - -const handleMouseLeave = (dispatch: AppDispatch): React.MouseEventHandler => () => { - dispatch(closeProfileHoverCard(true)); -}; - interface IProfileHoverCard { visible?: boolean; } @@ -52,10 +43,10 @@ const ProfileHoverCard: React.FC = ({ visible = true }) => { const history = useHistory(); const intl = useIntl(); + const { accountId, ref, updateAccountHoverCard, closeAccountHoverCard } = useAccountHoverCardStore(); + const me = useAppSelector(state => state.me); - const accountId: string | undefined = useAppSelector(state => state.profile_hover_card.accountId || undefined); - const { account } = useAccount(accountId, { withRelationship: true, withScrobble: true }); - const targetRef = useAppSelector(state => state.profile_hover_card.ref?.current); + const { account } = useAccount(accountId || undefined, { withRelationship: true, withScrobble: true }); const badges = getBadges(account); useEffect(() => { @@ -65,7 +56,7 @@ const ProfileHoverCard: React.FC = ({ visible = true }) => { useEffect(() => { const unlisten = history.listen(() => { showProfileHoverCard.cancel(); - dispatch(closeProfileHoverCard()); + closeAccountHoverCard(); }); return () => { @@ -76,7 +67,7 @@ const ProfileHoverCard: React.FC = ({ visible = true }) => { const { x, y, strategy, refs, context, placement } = useFloating({ open: !!account, elements: { - reference: targetRef, + reference: ref?.current, }, middleware: [ shift({ @@ -116,8 +107,8 @@ const ProfileHoverCard: React.FC = ({ visible = true }) => { left: x ?? 0, ...styles, }} - onMouseEnter={handleMouseEnter(dispatch)} - onMouseLeave={handleMouseLeave(dispatch)} + onMouseEnter={() => updateAccountHoverCard()} + onMouseLeave={() => closeAccountHoverCard()} > diff --git a/packages/pl-fe/src/components/sidebar-menu.tsx b/packages/pl-fe/src/components/sidebar-menu.tsx index 990327ae8..ec5c93fb3 100644 --- a/packages/pl-fe/src/components/sidebar-menu.tsx +++ b/packages/pl-fe/src/components/sidebar-menu.tsx @@ -6,13 +6,13 @@ import { Link, NavLink } from 'react-router-dom'; import { fetchOwnAccounts, logOut, switchAccount } from 'pl-fe/actions/auth'; import { getSettings } from 'pl-fe/actions/settings'; -import { closeSidebar } from 'pl-fe/actions/sidebar'; import { useAccount } from 'pl-fe/api/hooks'; import Account from 'pl-fe/components/account'; import { Stack, Divider, HStack, Icon, Text } from 'pl-fe/components/ui'; import ProfileStats from 'pl-fe/features/ui/components/profile-stats'; import { useAppDispatch, useAppSelector, useFeatures, useInstance, useRegistrationStatus } from 'pl-fe/hooks'; import { makeGetOtherAccounts } from 'pl-fe/selectors'; +import { useUiStore } from 'pl-fe/stores'; import sourceCode from 'pl-fe/utils/code'; import type { List as ImmutableList } from 'immutable'; @@ -79,18 +79,19 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { const intl = useIntl(); const dispatch = useAppDispatch(); + const { isSidebarOpen, closeSidebar } = useUiStore(); + const getOtherAccounts = useCallback(makeGetOtherAccounts(), []); const features = useFeatures(); const me = useAppSelector((state) => state.me); const { account } = useAccount(me || undefined); const otherAccounts: ImmutableList = useAppSelector((state) => getOtherAccounts(state)); - const sidebarOpen = useAppSelector((state) => state.sidebar.sidebarOpen); const settings = useAppSelector((state) => getSettings(state)); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size); const draftCount = useAppSelector((state) => state.draft_statuses.size); // const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); - const [sidebarVisible, setSidebarVisible] = useState(sidebarOpen); + const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen); const touchStart = useRef(0); const touchEnd = useRef(null); const { isOpen } = useRegistrationStatus(); @@ -102,11 +103,9 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { const [switcher, setSwitcher] = React.useState(false); - const onClose = () => dispatch(closeSidebar()); - const handleClose = () => { setSwitcher(false); - onClose(); + closeSidebar(); }; const handleSwitchAccount = (account: AccountEntity): React.MouseEventHandler => (e) => { @@ -157,17 +156,17 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { }, []); useEffect(() => { - if (sidebarOpen) containerRef.current?.querySelector('a')?.focus(); - setTimeout(() => setSidebarVisible(sidebarOpen), sidebarOpen ? 0 : 150); - }, [sidebarOpen]); + if (isSidebarOpen) containerRef.current?.querySelector('a')?.focus(); + setTimeout(() => setSidebarVisible(isSidebarOpen), isSidebarOpen ? 0 : 150); + }, [isSidebarOpen]); return (
{ >
{
@@ -197,7 +196,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => {
{account ? ( - + @@ -213,7 +212,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to={`/@${account.acct}`} icon={require('@tabler/icons/outline/user.svg')} text={intl.formatMessage(messages.profile)} - onClick={onClose} + onClick={closeSidebar} /> {(account.locked || followRequestsCount > 0) && ( @@ -221,7 +220,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/follow_requests' icon={require('@tabler/icons/outline/user-plus.svg')} text={intl.formatMessage(messages.followRequests)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -230,7 +229,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/conversations' icon={require('@tabler/icons/outline/mail.svg')} text={intl.formatMessage(messages.conversations)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -239,7 +238,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/bookmarks' icon={require('@tabler/icons/outline/bookmark.svg')} text={intl.formatMessage(messages.bookmarks)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -248,7 +247,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/groups' icon={require('@tabler/icons/outline/circles.svg')} text={intl.formatMessage(messages.groups)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -257,7 +256,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/lists' icon={require('@tabler/icons/outline/list.svg')} text={intl.formatMessage(messages.lists)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -266,7 +265,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/events' icon={require('@tabler/icons/outline/calendar-event.svg')} text={intl.formatMessage(messages.events)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -275,7 +274,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/directory' icon={require('@tabler/icons/outline/address-book.svg')} text={intl.formatMessage(messages.profileDirectory)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -284,7 +283,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/scheduled_statuses' icon={require('@tabler/icons/outline/calendar-stats.svg')} text={intl.formatMessage(messages.scheduledStatuses)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -293,7 +292,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/draft_statuses' icon={require('@tabler/icons/outline/notes.svg')} text={intl.formatMessage(messages.drafts)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -304,7 +303,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/timeline/local' icon={features.federating ? require('@tabler/icons/outline/affiliate.svg') : require('@tabler/icons/outline/world.svg')} text={features.federating ? : } - onClick={onClose} + onClick={closeSidebar} /> {features.bubbleTimeline && ( @@ -312,7 +311,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/timeline/bubble' icon={require('@tabler/icons/outline/chart-bubble.svg')} text={} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -321,7 +320,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/timeline/fediverse' icon={require('@tabler/icons/outline/topology-star-ring-3.svg')} text={} - onClick={onClose} + onClick={closeSidebar} /> )} } @@ -332,7 +331,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/settings/preferences' icon={require('@tabler/icons/outline/settings.svg')} text={intl.formatMessage(messages.preferences)} - onClick={onClose} + onClick={closeSidebar} /> {features.followedHashtagsList && ( @@ -340,7 +339,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/followed_tags' icon={require('@tabler/icons/outline/hash.svg')} text={intl.formatMessage(messages.followedTags)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -349,7 +348,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/developers' icon={require('@tabler/icons/outline/code.svg')} text={intl.formatMessage(messages.developers)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -358,7 +357,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/admin' icon={require('@tabler/icons/outline/dashboard.svg')} text={intl.formatMessage(messages.dashboard)} - onClick={onClose} + onClick={closeSidebar} // count={dashboardCount} WIP /> )} @@ -378,7 +377,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { href={sourceCode.url} icon={require('@tabler/icons/outline/code.svg')} text={intl.formatMessage(messages.sourceCode)} - onClick={onClose} + onClick={closeSidebar} /> @@ -419,7 +418,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/timeline/local' icon={features.federating ? require('@tabler/icons/outline/affiliate.svg') : require('@tabler/icons/outline/world.svg')} text={features.federating ? : } - onClick={onClose} + onClick={closeSidebar} /> {features.bubbleTimeline && !restrictUnauth.timelines.bubble && ( @@ -427,7 +426,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/timeline/bubble' icon={require('@tabler/icons/outline/chart-bubble.svg')} text={} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -436,7 +435,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/timeline/fediverse' icon={require('@tabler/icons/outline/topology-star-ring-3.svg')} text={} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -447,7 +446,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/login' icon={require('@tabler/icons/outline/login.svg')} text={intl.formatMessage(messages.login)} - onClick={onClose} + onClick={closeSidebar} /> {isOpen && ( @@ -455,7 +454,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { to='/signup' icon={require('@tabler/icons/outline/user-plus.svg')} text={intl.formatMessage(messages.register)} - onClick={onClose} + onClick={closeSidebar} /> )} @@ -465,7 +464,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { href={sourceCode.url} icon={require('@tabler/icons/outline/code.svg')} text={intl.formatMessage(messages.sourceCode)} - onClick={onClose} + onClick={closeSidebar} /> )} diff --git a/packages/pl-fe/src/components/status-hover-card.tsx b/packages/pl-fe/src/components/status-hover-card.tsx index 0323a688d..26254d0f0 100644 --- a/packages/pl-fe/src/components/status-hover-card.tsx +++ b/packages/pl-fe/src/components/status-hover-card.tsx @@ -4,10 +4,10 @@ import React, { useEffect, useCallback } from 'react'; import { useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import { closeStatusHoverCard, updateStatusHoverCard } from 'pl-fe/actions/status-hover-card'; import { fetchStatus } from 'pl-fe/actions/statuses'; import StatusContainer from 'pl-fe/containers/status-container'; import { useAppSelector, useAppDispatch } from 'pl-fe/hooks'; +import { useStatusHoverCardStore } from 'pl-fe/stores'; import { showStatusHoverCard } from './hover-status-wrapper'; import { Card, CardBody } from './ui'; @@ -22,9 +22,9 @@ const StatusHoverCard: React.FC = ({ visible = true }) => { const intl = useIntl(); const history = useHistory(); - const statusId: string | undefined = useAppSelector(state => state.status_hover_card.statusId || undefined); + const { statusId, ref, closeStatusHoverCard, updateStatusHoverCard } = useStatusHoverCardStore(); + const status = useAppSelector(state => state.statuses.get(statusId!)); - const targetRef = useAppSelector(state => state.status_hover_card.ref?.current); useEffect(() => { if (statusId && !status) { @@ -35,7 +35,7 @@ const StatusHoverCard: React.FC = ({ visible = true }) => { useEffect(() => { const unlisten = history.listen(() => { showStatusHoverCard.cancel(); - dispatch(closeStatusHoverCard()); + closeStatusHoverCard(); }); return () => { @@ -46,7 +46,7 @@ const StatusHoverCard: React.FC = ({ visible = true }) => { const { x, y, strategy, refs, context, placement } = useFloating({ open: !!statusId, elements: { - reference: targetRef, + reference: ref?.current, }, placement: 'top', middleware: [ @@ -69,11 +69,11 @@ const StatusHoverCard: React.FC = ({ visible = true }) => { }); const handleMouseEnter = useCallback((): React.MouseEventHandler => () => { - dispatch(updateStatusHoverCard()); + updateStatusHoverCard(); }, []); const handleMouseLeave = useCallback((): React.MouseEventHandler => () => { - dispatch(closeStatusHoverCard(true)); + closeStatusHoverCard(true); }, []); if (!statusId) return null; @@ -106,7 +106,7 @@ const StatusHoverCard: React.FC = ({ visible = true }) => { onMouseEnter={handleMouseEnter()} onMouseLeave={handleMouseLeave()} > - + {renderStatus(statusId)} diff --git a/packages/pl-fe/src/components/thumb-navigation.tsx b/packages/pl-fe/src/components/thumb-navigation.tsx index 8713e257c..819d27498 100644 --- a/packages/pl-fe/src/components/thumb-navigation.tsx +++ b/packages/pl-fe/src/components/thumb-navigation.tsx @@ -3,12 +3,11 @@ import { defineMessages, useIntl } from 'react-intl'; import { useRouteMatch } from 'react-router-dom'; import { groupComposeModal } from 'pl-fe/actions/compose'; -import { openSidebar } from 'pl-fe/actions/sidebar'; import ThumbNavigationLink from 'pl-fe/components/thumb-navigation-link'; import { useStatContext } from 'pl-fe/contexts/stat-context'; import { Entities } from 'pl-fe/entity-store/entities'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'pl-fe/hooks'; -import { useModalsStore } from 'pl-fe/stores'; +import { useModalsStore, useUiStore } from 'pl-fe/stores'; import { isStandalone } from 'pl-fe/utils/state'; import { Icon } from './ui'; @@ -30,14 +29,13 @@ const ThumbNavigation: React.FC = (): JSX.Element => { const match = useRouteMatch<{ groupId: string }>('/groups/:groupId'); + const { openSidebar } = useUiStore(); const { openModal } = useModalsStore(); const { unreadChatsCount } = useStatContext(); const standalone = useAppSelector(isStandalone); const notificationCount = useAppSelector((state) => state.notifications.unread); - const handleOpenSidebar = () => dispatch(openSidebar()); - const handleOpenComposeModal = () => { if (match?.params.groupId) { dispatch((_, getState) => { @@ -66,7 +64,7 @@ const ThumbNavigation: React.FC = (): JSX.Element => {
} { return ( } scrollKey={`quotes:${statusId}`} diff --git a/packages/pl-fe/src/features/remote-timeline/index.tsx b/packages/pl-fe/src/features/remote-timeline/index.tsx index e637048cf..7f12d23e7 100644 --- a/packages/pl-fe/src/features/remote-timeline/index.tsx +++ b/packages/pl-fe/src/features/remote-timeline/index.tsx @@ -66,7 +66,7 @@ const RemoteTimeline: React.FC = ({ params }) => { )} = ({ children }) => { const vapidKey = useAppSelector(state => getVapidKey(state)); const notificationFilter = useSettings().notifications.quickFilter.active as FilterType; - const { isOpen: dropdownMenuIsOpen } = useDropdownMenuStore(); + const { isDropdownMenuOpen } = useUiStore(); const standalone = useAppSelector(isStandalone); const { isDragging } = useDraggedFiles(node); @@ -454,7 +454,7 @@ const UI: React.FC = ({ children }) => { if (me === null) return null; const style: React.CSSProperties = { - pointerEvents: dropdownMenuIsOpen ? 'none' : undefined, + pointerEvents: isDropdownMenuOpen ? 'none' : undefined, }; return ( diff --git a/packages/pl-fe/src/reducers/index.ts b/packages/pl-fe/src/reducers/index.ts index 4f24b9e52..514c6faab 100644 --- a/packages/pl-fe/src/reducers/index.ts +++ b/packages/pl-fe/src/reducers/index.ts @@ -32,14 +32,11 @@ import onboarding from './onboarding'; import pending_statuses from './pending-statuses'; import plfe from './pl-fe'; import polls from './polls'; -import profile_hover_card from './profile-hover-card'; import push_notifications from './push-notifications'; import scheduled_statuses from './scheduled-statuses'; import search from './search'; import security from './security'; import settings from './settings'; -import sidebar from './sidebar'; -import status_hover_card from './status-hover-card'; import status_lists from './status-lists'; import statuses from './statuses'; import suggestions from './suggestions'; @@ -79,14 +76,11 @@ const reducers = { pending_statuses, plfe, polls, - profile_hover_card, push_notifications, scheduled_statuses, search, security, settings, - sidebar, - status_hover_card, status_lists, statuses, suggestions, diff --git a/packages/pl-fe/src/reducers/profile-hover-card.ts b/packages/pl-fe/src/reducers/profile-hover-card.ts deleted file mode 100644 index 8597cd008..000000000 --- a/packages/pl-fe/src/reducers/profile-hover-card.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Record as ImmutableRecord } from 'immutable'; - -import { - PROFILE_HOVER_CARD_OPEN, - PROFILE_HOVER_CARD_CLOSE, - PROFILE_HOVER_CARD_UPDATE, -} from 'pl-fe/actions/profile-hover-card'; - -import type { AnyAction } from 'redux'; - -const ReducerRecord = ImmutableRecord({ - ref: null as React.MutableRefObject | null, - accountId: '', - hovered: false, -}); - -type State = ReturnType; - -const profileHoverCard = (state: State = ReducerRecord(), action: AnyAction) => { - switch (action.type) { - case PROFILE_HOVER_CARD_OPEN: - return state.withMutations((state) => { - state.set('ref', action.ref); - state.set('accountId', action.accountId); - }); - case PROFILE_HOVER_CARD_UPDATE: - return state.set('hovered', true); - case PROFILE_HOVER_CARD_CLOSE: - if (state.get('hovered') === true && !action.force) - return state; - else - return ReducerRecord(); - default: - return state; - } -}; - -export { profileHoverCard as default }; diff --git a/packages/pl-fe/src/reducers/sidebar.test.ts b/packages/pl-fe/src/reducers/sidebar.test.ts deleted file mode 100644 index b9bd1274a..000000000 --- a/packages/pl-fe/src/reducers/sidebar.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import reducer from './sidebar'; - -describe('sidebar reducer', () => { - it('should return the initial state', () => { - expect(reducer(undefined, {} as any)).toEqual({ sidebarOpen: false }); - }); -}); diff --git a/packages/pl-fe/src/reducers/sidebar.ts b/packages/pl-fe/src/reducers/sidebar.ts deleted file mode 100644 index fc180087b..000000000 --- a/packages/pl-fe/src/reducers/sidebar.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { SIDEBAR_OPEN, SIDEBAR_CLOSE } from '../actions/sidebar'; - -import type { AnyAction } from 'redux'; - -type State = { - sidebarOpen: boolean; -}; - -const initialState: State = { - sidebarOpen: false, -}; - -const sidebar = (state: State = initialState, action: AnyAction): State => { - switch (action.type) { - case SIDEBAR_OPEN: - return { sidebarOpen: true }; - case SIDEBAR_CLOSE: - return { sidebarOpen: false }; - default: - return state; - } -}; - -export { sidebar as default }; diff --git a/packages/pl-fe/src/reducers/status-hover-card.test.tsx b/packages/pl-fe/src/reducers/status-hover-card.test.tsx deleted file mode 100644 index 4896a979a..000000000 --- a/packages/pl-fe/src/reducers/status-hover-card.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { - STATUS_HOVER_CARD_OPEN, - STATUS_HOVER_CARD_CLOSE, - STATUS_HOVER_CARD_UPDATE, -} from 'pl-fe/actions/status-hover-card'; - -import reducer, { ReducerRecord } from './status-hover-card'; - -describe(STATUS_HOVER_CARD_OPEN, () => { - it('sets the ref and statusId', () => { - const ref = { current: document.createElement('div') }; - - const action = { - type: STATUS_HOVER_CARD_OPEN, - ref, - statusId: '1234', - }; - - const result = reducer(undefined, action); - expect(result.ref).toBe(ref); - expect(result.statusId).toBe('1234'); - }); -}); - -describe(STATUS_HOVER_CARD_CLOSE, () => { - it('flushes the state', () => { - const state = ReducerRecord({ - ref: { current: document.createElement('div') }, - statusId: '1234', - }); - - const action = { type: STATUS_HOVER_CARD_CLOSE }; - - const result = reducer(state, action); - expect(result.ref).toBe(null); - expect(result.statusId).toBe(''); - }); - - it('leaves the state alone if hovered', () => { - const state = ReducerRecord({ - ref: { current: document.createElement('div') }, - statusId: '1234', - hovered: true, - }); - - const action = { type: STATUS_HOVER_CARD_CLOSE }; - const result = reducer(state, action); - expect(result).toEqual(state); - }); - - it('action.force flushes the state even if hovered', () => { - const state = ReducerRecord({ - ref: { current: document.createElement('div') }, - statusId: '1234', - hovered: true, - }); - - const action = { type: STATUS_HOVER_CARD_CLOSE, force: true }; - const result = reducer(state, action); - expect(result.ref).toBe(null); - expect(result.statusId).toBe(''); - }); -}); - -describe(STATUS_HOVER_CARD_UPDATE, () => { - it('sets hovered', () => { - const state = ReducerRecord(); - const action = { type: STATUS_HOVER_CARD_UPDATE }; - const result = reducer(state, action); - expect(result.hovered).toBe(true); - }); -}); diff --git a/packages/pl-fe/src/reducers/status-hover-card.ts b/packages/pl-fe/src/reducers/status-hover-card.ts deleted file mode 100644 index 06155242f..000000000 --- a/packages/pl-fe/src/reducers/status-hover-card.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Record as ImmutableRecord } from 'immutable'; - -import { - STATUS_HOVER_CARD_OPEN, - STATUS_HOVER_CARD_CLOSE, - STATUS_HOVER_CARD_UPDATE, -} from 'pl-fe/actions/status-hover-card'; - -import type { AnyAction } from 'redux'; - -const ReducerRecord = ImmutableRecord({ - ref: null as React.MutableRefObject | null, - statusId: '', - hovered: false, -}); - -type State = ReturnType; - -const statusHoverCard = (state: State = ReducerRecord(), action: AnyAction) => { - switch (action.type) { - case STATUS_HOVER_CARD_OPEN: - return state.withMutations((state) => { - state.set('ref', action.ref); - state.set('statusId', action.statusId); - }); - case STATUS_HOVER_CARD_UPDATE: - return state.set('hovered', true); - case STATUS_HOVER_CARD_CLOSE: - if (state.hovered === true && !action.force) - return state; - else - return ReducerRecord(); - default: - return state; - } -}; - -export { - ReducerRecord, - statusHoverCard as default, -}; diff --git a/packages/pl-fe/src/stores/account-hover-card.ts b/packages/pl-fe/src/stores/account-hover-card.ts new file mode 100644 index 000000000..f1390b4db --- /dev/null +++ b/packages/pl-fe/src/stores/account-hover-card.ts @@ -0,0 +1,31 @@ +import { create } from 'zustand'; + +type State = { + ref: React.MutableRefObject | null; + accountId: string | null; + hovered: boolean; + openAccountHoverCard: (ref: React.MutableRefObject, accountId: string) => void; + updateAccountHoverCard: () => void; + closeAccountHoverCard: (force?: boolean) => void; +} + +const useAccountHoverCardStore = create((set) => ({ + ref: null, + accountId: null, + hovered: false, + openAccountHoverCard: (ref, accountId) => set({ + ref, + accountId, + }), + updateAccountHoverCard: () => set({ + hovered: true, + }), + closeAccountHoverCard: (force = false) => set((state) => state.hovered && !force ? {} : { + ref: null, + accountId: null, + hovered: false, + }), +})); + +export { useAccountHoverCardStore }; + diff --git a/packages/pl-fe/src/stores/dropdown-menu.ts b/packages/pl-fe/src/stores/dropdown-menu.ts deleted file mode 100644 index bb55c6f78..000000000 --- a/packages/pl-fe/src/stores/dropdown-menu.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { create } from 'zustand'; - -type State = { - isOpen: boolean; - openDropdownMenu: () => void; - closeDropdownMenu: () => void; -} - -const useDropdownMenuStore = create((set) => ({ - isOpen: false, - openDropdownMenu: () => set({ isOpen: true }), - closeDropdownMenu: () => set({ isOpen: false }), -})); - -export { useDropdownMenuStore }; - diff --git a/packages/pl-fe/src/stores/index.ts b/packages/pl-fe/src/stores/index.ts index 98096eb5e..fc7b61c29 100644 --- a/packages/pl-fe/src/stores/index.ts +++ b/packages/pl-fe/src/stores/index.ts @@ -1,2 +1,4 @@ -export { useDropdownMenuStore } from './dropdown-menu'; +export { useAccountHoverCardStore } from './account-hover-card'; export { useModalsStore } from './modals'; +export { useStatusHoverCardStore } from './status-hover-card'; +export { useUiStore } from './ui'; diff --git a/packages/pl-fe/src/stores/status-hover-card.ts b/packages/pl-fe/src/stores/status-hover-card.ts new file mode 100644 index 000000000..d2b5be5a6 --- /dev/null +++ b/packages/pl-fe/src/stores/status-hover-card.ts @@ -0,0 +1,31 @@ +import { create } from 'zustand'; + +type State = { + ref: React.MutableRefObject | null; + statusId: string | null; + hovered: boolean; + openStatusHoverCard: (ref: React.MutableRefObject, statusId: string) => void; + updateStatusHoverCard: () => void; + closeStatusHoverCard: (force?: boolean) => void; +} + +const useStatusHoverCardStore = create((set) => ({ + ref: null, + statusId: null, + hovered: false, + openStatusHoverCard: (ref, statusId) => set({ + ref, + statusId, + }), + updateStatusHoverCard: () => set({ + hovered: true, + }), + closeStatusHoverCard: (force = false) => set((state) => state.hovered && !force ? {} : { + ref: null, + statusId: null, + hovered: false, + }), +})); + +export { useStatusHoverCardStore }; + diff --git a/packages/pl-fe/src/stores/ui.ts b/packages/pl-fe/src/stores/ui.ts new file mode 100644 index 000000000..1e57b00b4 --- /dev/null +++ b/packages/pl-fe/src/stores/ui.ts @@ -0,0 +1,22 @@ +import { create } from 'zustand'; + +type State = { + isDropdownMenuOpen: boolean; + openDropdownMenu: () => void; + closeDropdownMenu: () => void; + isSidebarOpen: boolean; + openSidebar: () => void; + closeSidebar: () => void; +} + +const useUiStore = create((set) => ({ + isDropdownMenuOpen: false, + openDropdownMenu: () => set({ isDropdownMenuOpen: true }), + closeDropdownMenu: () => set({ isDropdownMenuOpen: false }), + isSidebarOpen: false, + openSidebar: () => set({ isSidebarOpen: true }), + closeSidebar: () => set({ isSidebarOpen: false }), +})); + +export { useUiStore }; +