- {iconElement}
+
+ {active ? activeIconElement : iconElement}
{text}
{badgeElement}
@@ -32,6 +35,8 @@ const ColumnLink = ({ icon, iconComponent, text, to, href, method, badge, transp
ColumnLink.propTypes = {
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
iconComponent: PropTypes.func,
+ activeIcon: PropTypes.node,
+ activeIconComponent: PropTypes.func,
text: PropTypes.string.isRequired,
to: PropTypes.string,
href: PropTypes.string,
diff --git a/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx b/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx
deleted file mode 100644
index 4aa0092631..0000000000
--- a/app/javascript/mastodon/features/ui/components/follow_requests_column_link.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import PropTypes from 'prop-types';
-import { Component } from 'react';
-
-import { injectIntl, defineMessages } from 'react-intl';
-
-import { List as ImmutableList } from 'immutable';
-import { connect } from 'react-redux';
-
-import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
-import { fetchFollowRequests } from 'mastodon/actions/accounts';
-import { IconWithBadge } from 'mastodon/components/icon_with_badge';
-import ColumnLink from 'mastodon/features/ui/components/column_link';
-
-const messages = defineMessages({
- text: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
-});
-
-const mapStateToProps = state => ({
- count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
-});
-
-class FollowRequestsColumnLink extends Component {
-
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- count: PropTypes.number.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- componentDidMount () {
- const { dispatch } = this.props;
-
- dispatch(fetchFollowRequests());
- }
-
- render () {
- const { count, intl } = this.props;
-
- if (count === 0) {
- return null;
- }
-
- return (
- }
- text={intl.formatMessage(messages.text)}
- />
- );
- }
-
-}
-
-export default injectIntl(connect(mapStateToProps)(FollowRequestsColumnLink));
diff --git a/app/javascript/mastodon/features/ui/components/list_panel.jsx b/app/javascript/mastodon/features/ui/components/list_panel.jsx
index fec21f14ca..03c8fce9e8 100644
--- a/app/javascript/mastodon/features/ui/components/list_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/list_panel.jsx
@@ -1,10 +1,9 @@
-import PropTypes from 'prop-types';
+import { useEffect } from 'react';
import { createSelector } from '@reduxjs/toolkit';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { connect } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
+import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import { fetchLists } from 'mastodon/actions/lists';
@@ -18,40 +17,25 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(4);
});
-const mapStateToProps = state => ({
- lists: getOrderedLists(state),
-});
-
-class ListPanel extends ImmutablePureComponent {
+export const ListPanel = () => {
+ const dispatch = useDispatch();
+ const lists = useSelector(state => getOrderedLists(state));
- static propTypes = {
- dispatch: PropTypes.func.isRequired,
- lists: ImmutablePropTypes.list,
- };
-
- componentDidMount () {
- const { dispatch } = this.props;
+ useEffect(() => {
dispatch(fetchLists());
- }
-
- render () {
- const { lists } = this.props;
-
- if (!lists || lists.isEmpty()) {
- return null;
- }
-
- return (
-
-
+ }, [dispatch]);
- {lists.map(list => (
-
- ))}
-
- );
+ if (!lists || lists.isEmpty()) {
+ return null;
}
-}
+ return (
+
+
-export default connect(mapStateToProps)(ListPanel);
+ {lists.map(list => (
+
+ ))}
+
+ );
+};
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
index 4a56988191..deda3258b9 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
@@ -1,20 +1,33 @@
import PropTypes from 'prop-types';
-import { Component } from 'react';
+import { Component, useEffect } from 'react';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
+import { useSelector, useDispatch } from 'react-redux';
+
+
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
-import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
+import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
+import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
-import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
+import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
+import HomeIcon from '@/material-icons/400-24px/home.svg?react';
+import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
+import NotificationsActiveIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
+import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
+import PersonAddActiveIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
+import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
-import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
-import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
+import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
+import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react';
+import StarIcon from '@/material-icons/400-24px/star.svg?react';
+import { fetchFollowRequests } from 'mastodon/actions/accounts';
+import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
@@ -22,9 +35,7 @@ import { transientSingleColumn } from 'mastodon/is_mobile';
import ColumnLink from './column_link';
import DisabledAccountBanner from './disabled_account_banner';
-import FollowRequestsColumnLink from './follow_requests_column_link';
-import ListPanel from './list_panel';
-import NotificationsCounterIcon from './notifications_counter_icon';
+import { ListPanel } from './list_panel';
import SignInBanner from './sign_in_banner';
const messages = defineMessages({
@@ -42,8 +53,48 @@ const messages = defineMessages({
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' },
openedInClassicInterface: { id: 'navigation_bar.opened_in_classic_interface', defaultMessage: 'Posts, accounts, and other specific pages are opened by default in the classic web interface.' },
+ followRequests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
});
+const NotificationsLink = () => {
+ const count = useSelector(state => state.getIn(['notifications', 'unread']));
+ const intl = useIntl();
+
+ return (
+ }
+ activeIcon={}
+ text={intl.formatMessage(messages.notifications)}
+ />
+ );
+};
+
+const FollowRequestsLink = () => {
+ const count = useSelector(state => state.getIn(['user_lists', 'follow_requests', 'items'])?.size ?? 0);
+ const intl = useIntl();
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(fetchFollowRequests());
+ }, [dispatch]);
+
+ if (count === 0) {
+ return null;
+ }
+
+ return (
+ }
+ activeIcon={}
+ text={intl.formatMessage(messages.followRequests)}
+ />
+ );
+};
+
class NavigationPanel extends Component {
static contextTypes = {
@@ -87,9 +138,9 @@ class NavigationPanel extends Component {
{signedIn && (
<>
-
- } text={intl.formatMessage(messages.notifications)} />
-
+
+
+
>
)}
@@ -113,9 +164,9 @@ class NavigationPanel extends Component {
{signedIn && (
<>
-
-
-
+
+
+
diff --git a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js b/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js
deleted file mode 100644
index 7d59d616d8..0000000000
--- a/app/javascript/mastodon/features/ui/components/notifications_counter_icon.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { connect } from 'react-redux';
-
-import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
-import { IconWithBadge } from 'mastodon/components/icon_with_badge';
-
-
-const mapStateToProps = state => ({
- count: state.getIn(['notifications', 'unread']),
- id: 'bell',
- icon: NotificationsIcon,
-});
-
-export default connect(mapStateToProps)(IconWithBadge);