Skip to content

Commit

Permalink
feat(ui): New navigation (#32517)
Browse files Browse the repository at this point in the history
  • Loading branch information
juliajforesti authored Jul 12, 2024
1 parent 8d76339 commit 1b7b116
Show file tree
Hide file tree
Showing 257 changed files with 10,055 additions and 268 deletions.
7 changes: 7 additions & 0 deletions .changeset/lucky-beds-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/ui-client': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Feature Preview: New Navigation - `Header` and `Contextualbar` size improvements consistent with the new global `NavBar`
8 changes: 1 addition & 7 deletions apps/meteor/app/theme/client/imports/general/base_old.css
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@
}

& .start {
margin-top: 12px;
margin-top: 44px;

text-align: center;

Expand All @@ -794,12 +794,6 @@
& .editing .body {
border-radius: var(--border-radius);
}

&.has-leader {
& .wrapper {
padding-top: 57px;
}
}
}

.rcx-message {
Expand Down
73 changes: 73 additions & 0 deletions apps/meteor/client/NavBarV2/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useToolbar } from '@react-aria/toolbar';
import { NavBar as NavBarComponent, NavBarSection, NavBarGroup, NavBarDivider } from '@rocket.chat/fuselage';
import { usePermission, useTranslation, useUser } from '@rocket.chat/ui-contexts';
import React, { useRef } from 'react';

import { useIsCallEnabled, useIsCallReady } from '../contexts/CallContext';
import { useOmnichannelEnabled } from '../hooks/omnichannel/useOmnichannelEnabled';
import { useOmnichannelShowQueueLink } from '../hooks/omnichannel/useOmnichannelShowQueueLink';
import { useHasLicenseModule } from '../hooks/useHasLicenseModule';
import {
NavBarItemOmniChannelCallDialPad,
NavBarItemOmnichannelContact,
NavBarItemOmnichannelLivechatToggle,
NavBarItemOmnichannelQueue,
NavBarItemOmnichannelCallToggle,
} from './NavBarOmnichannelToolbar';
import { NavBarItemMarketPlaceMenu, NavBarItemAuditMenu, NavBarItemDirectoryPage, NavBarItemHomePage } from './NavBarPagesToolbar';
import { NavBarItemLoginPage, NavBarItemAdministrationMenu, UserMenu } from './NavBarSettingsToolbar';

const NavBar = () => {
const t = useTranslation();
const user = useUser();

const hasAuditLicense = useHasLicenseModule('auditing') === true;

const showOmnichannel = useOmnichannelEnabled();
const hasManageAppsPermission = usePermission('manage-apps');
const hasAccessMarketplacePermission = usePermission('access-marketplace');
const showMarketplace = hasAccessMarketplacePermission || hasManageAppsPermission;

const showOmnichannelQueueLink = useOmnichannelShowQueueLink();
const isCallEnabled = useIsCallEnabled();
const isCallReady = useIsCallReady();

const pagesToolbarRef = useRef(null);
const { toolbarProps: pagesToolbarProps } = useToolbar({ 'aria-label': t('Pages') }, pagesToolbarRef);

const omnichannelToolbarRef = useRef(null);
const { toolbarProps: omnichannelToolbarProps } = useToolbar({ 'aria-label': t('Omnichannel') }, omnichannelToolbarRef);

return (
<NavBarComponent aria-label='header'>
<NavBarSection>
<NavBarGroup role='toolbar' ref={pagesToolbarRef} {...pagesToolbarProps}>
<NavBarItemHomePage title={t('Home')} />
<NavBarItemDirectoryPage title={t('Directory')} />
{showMarketplace && <NavBarItemMarketPlaceMenu />}
{hasAuditLicense && <NavBarItemAuditMenu />}
</NavBarGroup>
{showOmnichannel && (
<>
<NavBarDivider />
<NavBarGroup role='toolbar' ref={omnichannelToolbarRef} {...omnichannelToolbarProps}>
{showOmnichannelQueueLink && <NavBarItemOmnichannelQueue title={t('Queue')} />}
{isCallReady && <NavBarItemOmniChannelCallDialPad />}
<NavBarItemOmnichannelContact title={t('Contacts')} />
{isCallEnabled && <NavBarItemOmnichannelCallToggle />}
<NavBarItemOmnichannelLivechatToggle />
</NavBarGroup>
</>
)}
</NavBarSection>
<NavBarSection>
<NavBarGroup aria-label={t('Workspace_and_user_settings')}>
<NavBarItemAdministrationMenu />
{user ? <UserMenu user={user} /> : <NavBarItemLoginPage />}
</NavBarGroup>
</NavBarSection>
</NavBarComponent>
);
};

export default NavBar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentPropsWithoutRef } from 'react';
import React from 'react';

import { useVoipOutboundStates } from '../../contexts/CallContext';
import { useDialModal } from '../../hooks/useDialModal';

type NavBarItemOmniChannelCallDialPadProps = ComponentPropsWithoutRef<typeof NavBarItem>;

const NavBarItemOmniChannelCallDialPad = (props: NavBarItemOmniChannelCallDialPadProps) => {
const t = useTranslation();

const { openDialModal } = useDialModal();

const { outBoundCallsAllowed, outBoundCallsEnabledForUser } = useVoipOutboundStates();

return (
<NavBarItem
icon='dialpad'
onClick={(): void => openDialModal()}
disabled={!outBoundCallsEnabledForUser}
aria-label={t('Open_Dialpad')}
data-tooltip={outBoundCallsAllowed ? t('New_Call') : t('New_Call_Premium_Only')}
{...props}
/>
);
};

export default NavBarItemOmniChannelCallDialPad;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ComponentPropsWithoutRef } from 'react';
import React from 'react';

import { useIsCallReady, useIsCallError } from '../../contexts/CallContext';
import NavBarItemOmnichannelCallToggleError from './NavBarItemOmnichannelCallToggleError';
import NavBarItemOmnichannelCallToggleLoading from './NavBarItemOmnichannelCallToggleLoading';
import NavBarItemOmnichannelCallToggleReady from './NavBarItemOmnichannelCallToggleReady';

type NavBarItemOmnichannelCallToggleProps = ComponentPropsWithoutRef<
typeof NavBarItemOmnichannelCallToggleError | typeof NavBarItemOmnichannelCallToggleLoading | typeof NavBarItemOmnichannelCallToggleReady
>;

const NavBarItemOmnichannelCallToggle = (props: NavBarItemOmnichannelCallToggleProps) => {
const isCallReady = useIsCallReady();
const isCallError = useIsCallError();
if (isCallError) {
return <NavBarItemOmnichannelCallToggleError {...props} />;
}

if (!isCallReady) {
return <NavBarItemOmnichannelCallToggleLoading {...props} />;
}

return <NavBarItemOmnichannelCallToggleReady {...props} />;
};

export default NavBarItemOmnichannelCallToggle;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentPropsWithoutRef } from 'react';
import React from 'react';

type NavBarItemOmnichannelCallToggleErrorProps = ComponentPropsWithoutRef<typeof NavBarItem>;

const NavBarItemOmnichannelCallToggleError = (props: NavBarItemOmnichannelCallToggleErrorProps) => {
const t = useTranslation();
return <NavBarItem icon='phone' danger data-tooltip={t('Error')} disabled {...props} />;
};

export default NavBarItemOmnichannelCallToggleError;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentPropsWithoutRef } from 'react';
import React from 'react';

type NavBarItemOmnichannelCallToggleLoadingProps = ComponentPropsWithoutRef<typeof NavBarItem>;

const NavBarItemOmnichannelCallToggleLoading = (props: NavBarItemOmnichannelCallToggleLoadingProps) => {
const t = useTranslation();
return <NavBarItem icon='phone' data-tooltip={t('Loading')} aria-label={t('VoIP_Toggle')} disabled {...props} />;
};

export default NavBarItemOmnichannelCallToggleLoading;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentPropsWithoutRef } from 'react';
import React, { useCallback } from 'react';

import { useCallerInfo, useCallRegisterClient, useCallUnregisterClient, useVoipNetworkStatus } from '../../contexts/CallContext';

type NavBarItemOmnichannelCallToggleReadyProps = ComponentPropsWithoutRef<typeof NavBarItem>;

const NavBarItemOmnichannelCallToggleReady = (props: NavBarItemOmnichannelCallToggleReadyProps) => {
const t = useTranslation();

const caller = useCallerInfo();
const unregister = useCallUnregisterClient();
const register = useCallRegisterClient();

const networkStatus = useVoipNetworkStatus();
const registered = !['ERROR', 'INITIAL', 'UNREGISTERED'].includes(caller.state);
const inCall = ['IN_CALL'].includes(caller.state);

const onClickVoipButton = useCallback((): void => {
if (registered) {
unregister();
return;
}
register();
}, [registered, register, unregister]);

const getTitle = (): string => {
if (networkStatus === 'offline') {
return t('Waiting_for_server_connection');
}

if (inCall) {
return t('Cannot_disable_while_on_call');
}

if (registered) {
return t('Turn_off_answer_calls');
}

return t('Turn_on_answer_calls');
};

const getIcon = (): 'phone-issue' | 'phone' | 'phone-disabled' => {
if (networkStatus === 'offline') {
return 'phone-issue';
}
return registered ? 'phone' : 'phone-disabled';
};

return (
<NavBarItem
icon={getIcon()}
disabled={inCall}
aria-checked={registered}
aria-label={t('VoIP_Toggle')}
data-tooltip={getTitle()}
{...props}
success={registered}
warning={networkStatus === 'offline'}
onClick={onClickVoipButton}
/>
);
};

export default NavBarItemOmnichannelCallToggleReady;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useRouter, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
import React from 'react';

type NavBarItemOmnichannelContactProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

const NavBarItemOmnichannelContact = (props: NavBarItemOmnichannelContactProps) => {
const router = useRouter();
const currentRoute = useCurrentRoutePath();

return (
<NavBarItem
{...props}
icon='address-book'
onClick={() => router.navigate('/omnichannel-directory')}
pressed={currentRoute?.includes('/omnichannel-directory')}
/>
);
};

export default NavBarItemOmnichannelContact;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Sidebar } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, ComponentProps } from 'react';
import React from 'react';

import { useOmnichannelAgentAvailable } from '../../hooks/omnichannel/useOmnichannelAgentAvailable';

type NavBarItemOmnichannelLivechatToggleProps = Omit<ComponentProps<typeof Sidebar.TopBar.Action>, 'icon'>;

const NavBarItemOmnichannelLivechatToggle = (props: NavBarItemOmnichannelLivechatToggleProps): ReactElement => {
const t = useTranslation();
const agentAvailable = useOmnichannelAgentAvailable();
const changeAgentStatus = useEndpoint('POST', '/v1/livechat/agent.status');
const dispatchToastMessage = useToastMessageDispatch();

const handleAvailableStatusChange = useEffectEvent(async () => {
try {
await changeAgentStatus({});
} catch (error: unknown) {
dispatchToastMessage({ type: 'error', message: error });
}
});

return (
<Sidebar.TopBar.Action
{...props}
id='omnichannel-status-toggle'
title={agentAvailable ? t('Turn_off_answer_chats') : t('Turn_on_answer_chats')}
success={agentAvailable}
icon={agentAvailable ? 'message' : 'message-disabled'}
onClick={handleAvailableStatusChange}
/>
);
};

export default NavBarItemOmnichannelLivechatToggle;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useRouter, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
import React from 'react';

type NavBarItemOmnichannelQueueProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

const NavBarItemOmnichannelQueue = (props: NavBarItemOmnichannelQueueProps) => {
const router = useRouter();
const currentRoute = useCurrentRoutePath();

return (
<NavBarItem
{...props}
icon='queue'
onClick={() => router.navigate('/livechat-queue')}
pressed={currentRoute?.includes('/livechat-queue')}
/>
);
};

export default NavBarItemOmnichannelQueue;
5 changes: 5 additions & 0 deletions apps/meteor/client/NavBarV2/NavBarOmnichannelToolbar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as NavBarItemOmniChannelCallDialPad } from './NavBarItemOmniChannelCallDialPad';
export { default as NavBarItemOmnichannelCallToggle } from './NavBarItemOmnichannelCallToggle';
export { default as NavBarItemOmnichannelContact } from './NavBarItemOmnichannelContact';
export { default as NavBarItemOmnichannelLivechatToggle } from './NavBarItemOmnichannelLivechatToggle';
export { default as NavBarItemOmnichannelQueue } from './NavBarItemOmnichannelQueue';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useCurrentRoutePath, useTranslation } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
import React from 'react';

import GenericMenu from '../../components/GenericMenu/GenericMenu';
import { useAuditMenu } from './hooks/useAuditMenu';

type NavBarItemAuditMenuProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

const NavBarItemAuditMenu = (props: NavBarItemAuditMenuProps) => {
const t = useTranslation();
const sections = useAuditMenu();
const currentRoute = useCurrentRoutePath();

return (
<GenericMenu
sections={sections}
title={t('Audit')}
is={NavBarItem}
placement='bottom-start'
icon='document-eye'
pressed={currentRoute?.includes('/audit')}
{...props}
/>
);
};

export default NavBarItemAuditMenu;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NavBarItem } from '@rocket.chat/fuselage';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import { useRouter, useCurrentRoutePath } from '@rocket.chat/ui-contexts';
import type { HTMLAttributes } from 'react';
import React from 'react';

type NavBarItemDirectoryPageProps = Omit<HTMLAttributes<HTMLElement>, 'is'>;

const NavBarItemDirectoryPage = (props: NavBarItemDirectoryPageProps) => {
const router = useRouter();
const handleDirectory = useEffectEvent(() => {
router.navigate('/directory');
});
const currentRoute = useCurrentRoutePath();

return <NavBarItem {...props} icon='notebook-hashtag' onClick={handleDirectory} pressed={currentRoute?.includes('/directory')} />;
};

export default NavBarItemDirectoryPage;
Loading

0 comments on commit 1b7b116

Please sign in to comment.