Skip to content

Commit

Permalink
feat: Room header keyboard navigability (#31516)
Browse files Browse the repository at this point in the history
  • Loading branch information
dougfabris authored Jan 23, 2024
1 parent c8ab658 commit 9cb9796
Show file tree
Hide file tree
Showing 25 changed files with 134 additions and 85 deletions.
8 changes: 8 additions & 0 deletions .changeset/blue-tomatoes-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/ui-client": minor
---

Room header keyboard navigability

![Kapture 2024-01-22 at 11 33 14](https://github.com/RocketChat/Rocket.Chat/assets/27704687/f116c1e6-4ec7-4175-a01b-fa98eade2416)
6 changes: 3 additions & 3 deletions apps/meteor/client/components/Page/PageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box, IconButton } from '@rocket.chat/fuselage';
import { HeaderToolbox, useDocumentTitle } from '@rocket.chat/ui-client';
import { HeaderToolbar, useDocumentTitle } from '@rocket.chat/ui-client';
import { useLayout, useTranslation } from '@rocket.chat/ui-contexts';
import type { FC, ComponentProps, ReactNode } from 'react';
import React, { useContext } from 'react';
Expand Down Expand Up @@ -31,9 +31,9 @@ const PageHeader: FC<PageHeaderProps> = ({ children = undefined, title, onClickB
>
<Box height='100%' marginInline={24} display='flex' flexDirection='row' flexWrap='wrap' alignItems='center' color='default'>
{isMobile && (
<HeaderToolbox>
<HeaderToolbar>
<BurgerMenu />
</HeaderToolbox>
</HeaderToolbar>
)}
{onClickBack && <IconButton small mie={8} icon='arrow-back' onClick={onClickBack} title={t('Back')} />}
<Box is='h1' fontScale='h2' flexGrow={1} data-qa-type='PageHeader-title'>
Expand Down
8 changes: 4 additions & 4 deletions apps/meteor/client/hooks/roomActions/useThreadRoomAction.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { BadgeProps } from '@rocket.chat/fuselage';
import { HeaderToolboxAction, HeaderToolboxActionBadge } from '@rocket.chat/ui-client';
import { HeaderToolbarAction, HeaderToolbarActionBadge } from '@rocket.chat/ui-client';
import { useSetting } from '@rocket.chat/ui-contexts';
import React, { lazy, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -46,7 +46,7 @@ export const useThreadRoomAction = () => {
tabComponent: Threads,
order: 2,
renderToolboxItem: ({ id, className, index, icon, title, toolbox: { tab }, action, disabled, tooltip }) => (
<HeaderToolboxAction
<HeaderToolbarAction
key={id}
className={className}
index={index}
Expand All @@ -58,8 +58,8 @@ export const useThreadRoomAction = () => {
disabled={disabled}
tooltip={tooltip}
>
{!!unread && <HeaderToolboxActionBadge variant={variant}>{unread}</HeaderToolboxActionBadge>}
</HeaderToolboxAction>
{!!unread && <HeaderToolbarActionBadge variant={variant}>{unread}</HeaderToolbarActionBadge>}
</HeaderToolbarAction>
),
};
}, [enabled, t, unread, variant]);
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/client/views/room/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isVoipRoom } from '@rocket.chat/core-typings';
import { HeaderToolbox } from '@rocket.chat/ui-client';
import { HeaderToolbar } from '@rocket.chat/ui-client';
import { useLayout } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React, { lazy, memo, useMemo } from 'react';
Expand All @@ -22,9 +22,9 @@ const Header = ({ room }: HeaderProps<IRoom>): ReactElement | null => {
const slots = useMemo(
() => ({
start: isMobile && (
<HeaderToolbox>
<HeaderToolbar>
<BurgerMenu />
</HeaderToolbox>
</HeaderToolbar>
),
}),
[isMobile],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { HeaderToolboxAction } from '@rocket.chat/ui-client';
import { HeaderToolbarAction } from '@rocket.chat/ui-client';
import { useRouter, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';
Expand All @@ -26,5 +26,5 @@ export const BackButton = ({ routeName }: { routeName?: string }): ReactElement
}
});

return <HeaderToolboxAction title={t('Back')} icon='back' onClick={back} />;
return <HeaderToolbarAction title={t('Back')} icon='back' onClick={back} />;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HeaderToolbox } from '@rocket.chat/ui-client';
import { HeaderToolbar } from '@rocket.chat/ui-client';
import { useLayout, useRouter } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React, { useCallback, useMemo } from 'react';
Expand Down Expand Up @@ -40,10 +40,10 @@ const OmnichannelRoomHeader: FC<OmnichannelRoomHeaderProps> = ({ slots: parentSl
() => ({
...parentSlot,
start: (!!isMobile || currentRouteName === 'omnichannel-directory' || currentRouteName === 'omnichannel-current-chats') && (
<HeaderToolbox>
<HeaderToolbar>
{isMobile && <BurgerMenu />}
<BackButton routeName={currentRouteName} />
</HeaderToolbox>
</HeaderToolbar>
),
posContent: <QuickActions />,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
import { Box, Dropdown, Option } from '@rocket.chat/fuselage';
import { HeaderToolboxAction } from '@rocket.chat/ui-client';
import { HeaderToolbarAction } from '@rocket.chat/ui-client';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { memo, useRef } from 'react';

Expand All @@ -26,7 +26,7 @@ const QuickActionOptions = ({ options, room, action, ...props }: QuickActionOpti

return (
<>
<HeaderToolboxAction ref={reference} onClick={(): void => toggle()} secondary={isVisible} {...props} />
<HeaderToolbarAction ref={reference} onClick={(): void => toggle()} secondary={isVisible} {...props} />
{isVisible && (
<Dropdown reference={reference} ref={target}>
{options.map(({ id, label, validate }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Box } from '@rocket.chat/fuselage';
import { HeaderToolbox, HeaderToolboxAction, HeaderToolboxDivider } from '@rocket.chat/ui-client';
import { HeaderToolbar, HeaderToolbarAction, HeaderToolbarDivider } from '@rocket.chat/ui-client';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import React, { memo } from 'react';
Expand All @@ -18,7 +18,7 @@ const QuickActions = ({ className }: QuickActionsProps) => {
const { quickActions, actionDefault } = useQuickActions();

return (
<HeaderToolbox aria-label={t('Omnichannel_quick_actions')}>
<HeaderToolbar aria-label={t('Omnichannel_quick_actions')}>
{quickActions.map(({ id, color, icon, title, action = actionDefault, options }, index) => {
const props = {
id,
Expand All @@ -36,10 +36,10 @@ const QuickActions = ({ className }: QuickActionsProps) => {
return <QuickActionOptions options={options} {...props} key={id} />;
}

return <HeaderToolboxAction {...props} key={id} />;
return <HeaderToolbarAction {...props} key={id} />;
})}
{quickActions.length > 0 && <HeaderToolboxDivider />}
</HeaderToolbox>
{quickActions.length > 0 && <HeaderToolbarDivider />}
</HeaderToolbar>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IVoipRoom } from '@rocket.chat/core-typings';
import { HeaderToolbox } from '@rocket.chat/ui-client';
import { HeaderToolbar } from '@rocket.chat/ui-client';
import { useLayout, useRouter } from '@rocket.chat/ui-contexts';
import type { FC } from 'react';
import React, { useCallback, useMemo } from 'react';
Expand Down Expand Up @@ -29,10 +29,10 @@ const VoipRoomHeader: FC<VoipRoomHeaderProps> = ({ slots: parentSlot, room }) =>
() => ({
...parentSlot,
start: (!!isMobile || currentRouteName === 'omnichannel-directory') && (
<HeaderToolbox>
<HeaderToolbar>
{isMobile && <BurgerMenu />}
{currentRouteName === 'omnichannel-directory' && <BackButton />}
</HeaderToolbox>
</HeaderToolbar>
),
}),
[isMobile, currentRouteName, parentSlot],
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/client/views/room/Header/RoomHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { Header, HeaderAvatar, HeaderContent, HeaderContentRow, HeaderSubtitle, HeaderToolbox } from '@rocket.chat/ui-client';
import { Header, HeaderAvatar, HeaderContent, HeaderContentRow, HeaderSubtitle, HeaderToolbar } from '@rocket.chat/ui-client';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { Suspense } from 'react';

Expand Down Expand Up @@ -63,11 +63,11 @@ const RoomHeader = ({ room, topic = '', slots = {} }: RoomHeaderProps) => {
</HeaderContent>
{slots?.posContent}
<Suspense fallback={null}>
<HeaderToolbox aria-label={t('Toolbox_room_actions')}>
<HeaderToolbar aria-label={t('Toolbox_room_actions')}>
{slots?.toolbox?.pre}
{slots?.toolbox?.content || <RoomToolbox />}
{slots?.toolbox?.pos}
</HeaderToolbox>
</HeaderToolbar>
</Suspense>
{slots?.end}
</Header>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Box } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { HeaderToolboxAction, HeaderToolboxDivider } from '@rocket.chat/ui-client';
import { HeaderToolbarAction, HeaderToolbarDivider } from '@rocket.chat/ui-client';
import { useLayout, useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import React, { memo } from 'react';
Expand Down Expand Up @@ -60,7 +60,7 @@ const RoomToolbox = ({ className }: RoomToolboxProps) => {
const renderDefaultToolboxItem: RoomToolboxActionConfig['renderToolboxItem'] = useMutableCallback(
({ id, className, index, icon, title, toolbox: { tab }, action, disabled, tooltip }) => {
return (
<HeaderToolboxAction
<HeaderToolbarAction
key={id}
className={className}
index={index}
Expand Down Expand Up @@ -89,7 +89,7 @@ const RoomToolbox = ({ className }: RoomToolboxProps) => {
return (
<>
{featuredActions.map(mapToToolboxItem)}
{featuredActions.length > 0 && <HeaderToolboxDivider />}
{featuredActions.length > 0 && <HeaderToolbarDivider />}
{visibleActions.map(mapToToolboxItem)}
{(normalActions.length > 6 || !roomToolboxExpanded) && !!hiddenActions.length && (
<GenericMenu title={t('Options')} data-qa-id='ToolBox-Menu' sections={hiddenActions} placement='bottom-end' />
Expand Down
6 changes: 3 additions & 3 deletions apps/meteor/client/views/room/RoomNotFound.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box } from '@rocket.chat/fuselage';
import { Header, HeaderToolbox } from '@rocket.chat/ui-client';
import { Header, HeaderToolbar } from '@rocket.chat/ui-client';
import { useLayout, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';
Expand All @@ -17,9 +17,9 @@ const RoomNotFound = (): ReactElement => {
header={
isMobile && (
<Header justifyContent='start'>
<HeaderToolbox>
<HeaderToolbar>
<BurgerMenu />
</HeaderToolbox>
</HeaderToolbar>
</Header>
)
}
Expand Down
23 changes: 23 additions & 0 deletions apps/meteor/tests/e2e/channel-management.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,29 @@ test.describe.serial('channel-management', () => {
await page.goto('/home');
});

test('should navigate on toolbar using arrow keys', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.content.sendMessage('hello composer');
await poHomeChannel.roomHeaderFavoriteBtn.focus();

await page.keyboard.press('Tab');
await page.keyboard.press('ArrowRight');
await page.keyboard.press('ArrowRight');

await expect(poHomeChannel.roomHeaderToolbar.getByRole('button', { name: 'Threads' })).toBeFocused();
});

test('should move the focus away from toolbar using tab key', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.content.sendMessage('hello composer');
await poHomeChannel.roomHeaderFavoriteBtn.focus();

await page.keyboard.press('Tab');
await page.keyboard.press('Tab');

await expect(poHomeChannel.roomHeaderToolbar.getByRole('button', { name: 'Call' })).not.toBeFocused();
});

test('expect add "user1" to "targetChannel"', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.tabs.btnTabMembers.click();
Expand Down
8 changes: 8 additions & 0 deletions apps/meteor/tests/e2e/page-objects/home-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@ export class HomeChannel {
get composerToolbarActions(): Locator {
return this.page.locator('[role=toolbar][aria-label="Composer Primary Actions"] button');
}

get roomHeaderFavoriteBtn(): Locator {
return this.page.getByRole('button', { name: 'Favorite' });
}

get roomHeaderToolbar(): Locator {
return this.page.locator('[role=toolbar][aria-label="Primary Room actions"]');
}
}
2 changes: 2 additions & 0 deletions packages/ui-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"devDependencies": {
"@babel/core": "~7.22.20",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.44.2",
"@rocket.chat/fuselage-hooks": "^0.33.0",
Expand Down Expand Up @@ -57,6 +58,7 @@
"/dist"
],
"peerDependencies": {
"@react-aria/toolbar": "*",
"@rocket.chat/css-in-js": "*",
"@rocket.chat/fuselage": "*",
"@rocket.chat/fuselage-hooks": "*",
Expand Down
60 changes: 30 additions & 30 deletions packages/ui-client/src/components/Header/Header.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
HeaderContent,
HeaderContentRow,
HeaderIcon,
HeaderToolbox,
HeaderToolboxAction,
HeaderToolboxActionBadge,
HeaderToolbar,
HeaderToolbarAction,
HeaderToolbarActionBadge,
HeaderTitle,
HeaderState,
HeaderSubtitle,
Expand All @@ -25,8 +25,8 @@ export default {
title: 'Components/Header',
component: Header,
subcomponents: {
HeaderToolbox,
HeaderToolboxAction,
HeaderToolbar,
HeaderToolbarAction,
HeaderAvatar,
HeaderContent,
HeaderContentRow,
Expand Down Expand Up @@ -103,19 +103,19 @@ export const Default = () => (
<HeaderSubtitle>{room.name}</HeaderSubtitle>
</HeaderContentRow>
</HeaderContent>
<HeaderToolbox>
<HeaderToolboxAction icon='magnifier' />
<HeaderToolboxAction icon='key' />
<HeaderToolboxAction icon='kebab' />
</HeaderToolbox>
<HeaderToolbar>
<HeaderToolbarAction icon='magnifier' />
<HeaderToolbarAction icon='key' />
<HeaderToolbarAction icon='kebab' />
</HeaderToolbar>
</Header>
);

export const WithBurger = () => (
<Header>
<HeaderToolbox>
<HeaderToolboxAction icon='burger' />
</HeaderToolbox>
<HeaderToolbar>
<HeaderToolbarAction icon='burger' />
</HeaderToolbar>
<HeaderAvatar>{avatar}</HeaderAvatar>
<HeaderContent>
<HeaderContentRow>
Expand All @@ -129,11 +129,11 @@ export const WithBurger = () => (
<HeaderSubtitle>{room.name}</HeaderSubtitle>
</HeaderContentRow>
</HeaderContent>
<HeaderToolbox>
<HeaderToolboxAction icon='magnifier' />
<HeaderToolboxAction icon='key' />
<HeaderToolboxAction icon='kebab' />
</HeaderToolbox>
<HeaderToolbar>
<HeaderToolbarAction icon='magnifier' />
<HeaderToolbarAction icon='key' />
<HeaderToolbarAction icon='kebab' />
</HeaderToolbar>
</Header>
);

Expand All @@ -150,17 +150,17 @@ export const WithActionBadge = () => (
<HeaderSubtitle>{room.name}</HeaderSubtitle>
</HeaderContentRow>
</HeaderContent>
<HeaderToolbox>
<HeaderToolboxAction icon='phone'>
<HeaderToolboxActionBadge variant='primary'>1</HeaderToolboxActionBadge>
</HeaderToolboxAction>
<HeaderToolboxAction icon='phone'>
<HeaderToolboxActionBadge variant='danger'>2</HeaderToolboxActionBadge>
</HeaderToolboxAction>
<HeaderToolboxAction icon='phone'>
<HeaderToolboxActionBadge variant='warning'>99</HeaderToolboxActionBadge>
</HeaderToolboxAction>
<HeaderToolboxAction icon='kebab' />
</HeaderToolbox>
<HeaderToolbar>
<HeaderToolbarAction icon='phone'>
<HeaderToolbarActionBadge variant='primary'>1</HeaderToolbarActionBadge>
</HeaderToolbarAction>
<HeaderToolbarAction icon='phone'>
<HeaderToolbarActionBadge variant='danger'>2</HeaderToolbarActionBadge>
</HeaderToolbarAction>
<HeaderToolbarAction icon='phone'>
<HeaderToolbarActionBadge variant='warning'>99</HeaderToolbarActionBadge>
</HeaderToolbarAction>
<HeaderToolbarAction icon='kebab' />
</HeaderToolbar>
</Header>
);
Loading

0 comments on commit 9cb9796

Please sign in to comment.