diff --git a/web/sidebar/app-switcher.react.js b/web/sidebar/app-switcher.react.js index b5f3cedf6..76c622436 100644 --- a/web/sidebar/app-switcher.react.js +++ b/web/sidebar/app-switcher.react.js @@ -1,11 +1,139 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; +import { useDispatch } from 'react-redux'; +import { + mostRecentReadThreadSelector, + unreadCount, +} from 'lib/selectors/thread-selectors'; + +import { useSelector } from '../redux/redux-utils'; +import SWMansionIcon from '../SWMansionIcon.react'; +import { updateNavInfoActionType } from '../types/nav-types'; +import css from './left-layout-aside.css'; import NavigationPanel from './navigation-panel.react'; function AppSwitcher(): React.Node { - return ; + const activeChatThreadID = useSelector( + state => state.navInfo.activeChatThreadID, + ); + const mostRecentReadThread = useSelector(mostRecentReadThreadSelector); + const activeThreadCurrentlyUnread = useSelector( + state => + !activeChatThreadID || + !!state.threadStore.threadInfos[activeChatThreadID]?.currentUser.unread, + ); + + const dispatch = useDispatch(); + + const onClickChat = React.useCallback( + (event: SyntheticEvent) => { + event.preventDefault(); + dispatch({ + type: updateNavInfoActionType, + payload: { + tab: 'chat', + activeChatThreadID: activeThreadCurrentlyUnread + ? mostRecentReadThread + : activeChatThreadID, + }, + }); + }, + [ + dispatch, + activeThreadCurrentlyUnread, + mostRecentReadThread, + activeChatThreadID, + ], + ); + + const viewerID = useSelector( + state => state.currentUserInfo && state.currentUserInfo.id, + ); + + const boundUnreadCount = useSelector(unreadCount); + + invariant(viewerID, 'should be set'); + let chatBadge = null; + if (boundUnreadCount > 0) { + chatBadge = {boundUnreadCount}; + } + + const chatNavigationItem = React.useMemo( + () => ( + + + + + {chatBadge} + + Chat + + + ), + [chatBadge, onClickChat], + ); + + const onClickCalendar = React.useCallback( + (event: SyntheticEvent) => { + event.preventDefault(); + dispatch({ + type: updateNavInfoActionType, + payload: { tab: 'calendar' }, + }); + }, + [dispatch], + ); + + const isCalendarEnabled = useSelector(state => state.enabledApps.calendar); + const calendarNavigationItem = React.useMemo(() => { + if (!isCalendarEnabled) { + return null; + } + return ( + + + + Calendar + + + ); + }, [isCalendarEnabled, onClickCalendar]); + + const onClickApps = React.useCallback( + (event: SyntheticEvent) => { + event.preventDefault(); + dispatch({ + type: updateNavInfoActionType, + payload: { + tab: 'apps', + }, + }); + }, + [dispatch], + ); + + const appNavigationItem = React.useMemo( + () => ( + + + + Apps + + + ), + [onClickApps], + ); + + return ( + + {chatNavigationItem} + {calendarNavigationItem} + {appNavigationItem} + + ); } export default AppSwitcher; diff --git a/web/sidebar/left-layout-aside.css b/web/sidebar/left-layout-aside.css index fae168bc2..bc318c2ba 100644 --- a/web/sidebar/left-layout-aside.css +++ b/web/sidebar/left-layout-aside.css @@ -1,68 +1,68 @@ .container { grid-area: sBar; background: var(--bg); color: var(--color); border-right: 1px solid var(--border-color); display: flex; } .navigationPanelContainer { width: 160px; } .container ul { list-style-type: none; padding-top: 24px; padding-left: 28px; } .container ul li { cursor: pointer; padding-bottom: 24px; line-height: 0; } .container p { display: flex; align-content: center; } .container ul a { color: var(--color-disabled); } .container svg { color: var(--color-disabled); padding-right: 12px; } .chatIconWrapper { position: relative; } span.chatBadge { position: absolute; top: -4px; left: 13px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 16px; height: 16px; color: var(--fg); background: var(--unread-bg); border-radius: 13px; font-size: 8px; line-height: 1.25; } -p.current-tab svg { +li.current-tab svg { color: var(--fg); } -p.current-tab a, -p.current-tab svg { +li.current-tab a, +li.current-tab svg { fill: var(--fg); color: var(--fg); } diff --git a/web/sidebar/navigation-panel.react.js b/web/sidebar/navigation-panel.react.js index ebcaca1cf..c9c600e10 100644 --- a/web/sidebar/navigation-panel.react.js +++ b/web/sidebar/navigation-panel.react.js @@ -1,138 +1,62 @@ // @flow import classNames from 'classnames'; -import invariant from 'invariant'; import * as React from 'react'; -import { useDispatch } from 'react-redux'; - -import { - mostRecentReadThreadSelector, - unreadCount, -} from 'lib/selectors/thread-selectors'; import { useSelector } from '../redux/redux-utils'; -import SWMansionIcon from '../SWMansionIcon.react'; -import { updateNavInfoActionType } from '../types/nav-types'; +import type { NavigationTab } from '../types/nav-types'; import css from './left-layout-aside.css'; -function NavigationPanel(): React.Node { - const activeChatThreadID = useSelector( - state => state.navInfo.activeChatThreadID, - ); - const navInfo = useSelector(state => state.navInfo); - const mostRecentReadThread = useSelector(mostRecentReadThreadSelector); - const activeThreadCurrentlyUnread = useSelector( - state => - !activeChatThreadID || - !!state.threadStore.threadInfos[activeChatThreadID]?.currentUser.unread, - ); - const viewerID = useSelector( - state => state.currentUserInfo && state.currentUserInfo.id, - ); - - const isCalendarEnabled = useSelector(state => state.enabledApps.calendar); +type NavigationPanelItemProps = { + +tab: NavigationTab, + +children: React.Node, +}; - const dispatch = useDispatch(); +function NavigationPanelItem(props: NavigationPanelItemProps): React.Node { + const { children } = props; + return children; +} - const onClickCalendar = React.useCallback( - (event: SyntheticEvent) => { - event.preventDefault(); - dispatch({ - type: updateNavInfoActionType, - payload: { tab: 'calendar' }, - }); - }, - [dispatch], - ); +type NavigationPanelContainerProps = { + +children: React.ChildrenArray>, +}; - const onClickChat = React.useCallback( - (event: SyntheticEvent) => { - event.preventDefault(); - dispatch({ - type: updateNavInfoActionType, - payload: { - tab: 'chat', - activeChatThreadID: activeThreadCurrentlyUnread - ? mostRecentReadThread - : activeChatThreadID, - }, - }); - }, - [ - dispatch, - activeThreadCurrentlyUnread, - mostRecentReadThread, - activeChatThreadID, - ], - ); +function NavigationPanelContainer( + props: NavigationPanelContainerProps, +): React.Node { + const { children } = props; + const navInfo = useSelector(state => state.navInfo); - const onClickApps = React.useCallback( - (event: SyntheticEvent) => { - event.preventDefault(); - dispatch({ - type: updateNavInfoActionType, - payload: { - tab: 'apps', - }, - }); - }, - [dispatch], + const items = React.useMemo( + () => + React.Children.map(children, child => { + if (!child) { + return null; + } + return ( + + {child} + + ); + }), + [children, navInfo.tab], ); - const boundUnreadCount = useSelector(unreadCount); - - invariant(viewerID, 'should be set'); - let chatBadge = null; - if (boundUnreadCount > 0) { - chatBadge = {boundUnreadCount}; - } - - const chatNavClasses = classNames({ - [css['current-tab']]: navInfo.tab === 'chat', - }); - const appsNavClasses = classNames({ - [css['current-tab']]: navInfo.tab === 'apps', - }); - - const calendarLink = React.useMemo(() => { - if (!isCalendarEnabled) { - return null; - } - const calendarNavClasses = classNames({ - [css['current-tab']]: navInfo.tab === 'calendar', - }); - return ( - - - - Calendar - - - ); - }, [isCalendarEnabled, navInfo.tab, onClickCalendar]); - return ( - - - - - - {chatBadge} - - Chat - - - {calendarLink} - - - - Apps - - - + {items} ); } +const NavigationPanel = { + Item: NavigationPanelItem, + Container: NavigationPanelContainer, +}; + export default NavigationPanel;
+ + + {chatBadge} + + Chat +
+ + Calendar +
+ + Apps +
- - Calendar -
- - - {chatBadge} - - Chat -
- - Apps -