Skip to content

Commit

Permalink
Arrow controls (#19239)
Browse files Browse the repository at this point in the history
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
  • Loading branch information
gabriellsh and ggazzo committed Oct 13, 2020
1 parent 8c3b494 commit c0b726f
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 27 deletions.
2 changes: 1 addition & 1 deletion app/theme/client/imports/components/sidebar/rooms-list.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
display: flex;

overflow-x: hidden;
overflow-y: scroll;
overflow-y: hidden;

flex: 1 1 auto;

Expand Down
13 changes: 10 additions & 3 deletions client/components/sidebar/Chats.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ const sections = {
Omnichannel,
};

const style = {
overflow: 'scroll',
};

export const useChatRoomTemplate = (sidebarViewMode) => useMemo(() => {
switch (sidebarViewMode) {
case 'extended':
Expand Down Expand Up @@ -183,7 +187,7 @@ export default () => {
return unread.add(room);
}

if (showDiscussion && room.drid) {
if (showDiscussion && room.prid) {
return discussion.add(room);
}

Expand Down Expand Up @@ -248,6 +252,7 @@ export default () => {
overscanCount={10}
width='100%'
ref={listRef}
style={style}
>
{Row}
</List>
Expand All @@ -270,13 +275,14 @@ const getMessage = (room, username, lastMessage, t) => {
return `${ lastMessage.u.name || lastMessage.u.username }: ${ normalizeThreadMessage(lastMessage) }`;
};

export const SideBarItemTemplateWithData = React.memo(({ room, username, extended, selected, SideBarItemTemplate, AvatarTemplate, t, style }) => {
export const SideBarItemTemplateWithData = React.memo(({ room, id, username, extended, selected, SideBarItemTemplate, AvatarTemplate, t, style }) => {
const title = roomTypes.getRoomName(room.t, room);
const icon = <SidebarIcon room={room}/>;
const href = roomTypes.getRouteLink(room.t, room);

const {
lastMessage,
hideUnreadStatus,
unread = 0,
alert,
userMentions,
Expand All @@ -297,9 +303,10 @@ export const SideBarItemTemplateWithData = React.memo(({ room, username, extende

return <SideBarItemTemplate
is='a'
id={id}
data-qa='sidebar-item'
aria-level='2'
unread={alert || unread}
unread={!hideUnreadStatus && (alert || unread)}
threadUnread={threadUnread}
selected={selected}
href={href}
Expand Down
104 changes: 89 additions & 15 deletions client/components/sidebar/SearchList.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useMemo, useEffect } from 'react';
import React, { useState, useMemo, useEffect, useRef } from 'react';
import { Meteor } from 'meteor/meteor';
import { Sidebar, TextInput, Box, Icon } from '@rocket.chat/fuselage';
import { useMutableCallback, useDebouncedValue, useStableArray, useResizeObserver, useAutoFocus } from '@rocket.chat/fuselage-hooks';
import { useMutableCallback, useDebouncedValue, useStableArray, useResizeObserver, useAutoFocus, useUniqueId } from '@rocket.chat/fuselage-hooks';
import memoize from 'memoize-one';
import { css } from '@rocket.chat/css-in-js';
import { FixedSizeList as List } from 'react-window';
Expand All @@ -15,30 +15,32 @@ import { roomTypes } from '../../../app/utils';
import { useUserPreference, useUserSubscriptions } from '../../contexts/UserContext';
import { useChatRoomTemplate, useAvatarTemplate, itemSizeMap, SideBarItemTemplateWithData } from './Chats';

const createItemData = memoize((items, t, SideBarItemTemplate, AvatarTemplate, useRealName) => ({
const createItemData = memoize((items, t, SideBarItemTemplate, AvatarTemplate, useRealName, extended) => ({
items,
t,
SideBarItemTemplate,
AvatarTemplate,
useRealName,
extended,
}));

const Row = React.memo(({ data, index, style }) => {
const { items, t, SideBarItemTemplate, AvatarTemplate, useRealName } = data;
const { items, t, SideBarItemTemplate, AvatarTemplate, useRealName, extended } = data;
const item = items[index];
if (item.t === 'd' && !item.u) {
return <UserItem useRealName={useRealName} style={style} t={t} item={item} SideBarItemTemplate={SideBarItemTemplate} AvatarTemplate={AvatarTemplate} />;
return <UserItem id={`search-${ item._id }`} useRealName={useRealName} style={style} t={t} item={item} SideBarItemTemplate={SideBarItemTemplate} AvatarTemplate={AvatarTemplate} />;
}
return <SideBarItemTemplateWithData style={style} t={t} room={item} SideBarItemTemplate={SideBarItemTemplate} AvatarTemplate={AvatarTemplate} />;
return <SideBarItemTemplateWithData id={`search-${ item._id }`} tabIndex={-1} extended={extended} style={style} t={t} room={item} SideBarItemTemplate={SideBarItemTemplate} AvatarTemplate={AvatarTemplate} />;
});

const UserItem = React.memo(({ item, style, t, SideBarItemTemplate, AvatarTemplate, useRealName }) => {
const UserItem = React.memo(({ item, id, style, t, SideBarItemTemplate, AvatarTemplate, useRealName }) => {
const title = useRealName ? item.fname || item.name : item.name || item.fname;
const icon = <Sidebar.Item.Icon name={roomTypes.getIcon(item)}/>;
const href = roomTypes.getRouteLink(item.t, item);

return <SideBarItemTemplate
is='a'
id={id}
href={href}
title={title}
subtitle={t('No_messages_yet')}
Expand Down Expand Up @@ -148,61 +150,133 @@ const useInput = (initial) => {
const onChange = useMutableCallback((e) => {
setValue(e.currentTarget.value);
});
return { value, onChange };
return { value, onChange, setValue };
};

const toggleSelectionState = (next, current, input) => {
input.setAttribute('aria-activedescendant', next.id);
next.setAttribute('aria-selected', true);
next.classList.add('rcx-sidebar-item--selected');
if (current) {
current.setAttribute('aria-selected', false);
current.classList.remove('rcx-sidebar-item--selected');
}
};

const SearchList = React.forwardRef(function SearchList({ onClose }, ref) {
const listId = useUniqueId();
const t = useTranslation();
const filter = useInput('');
const { setValue: setFilterValue, ...filter } = useInput('');

const autofocus = useAutoFocus();

const listRef = useRef();

const selectedElement = useRef();
const itemIndexRef = useRef(0);

const sidebarViewMode = useUserPreference('sidebarViewMode');
const sidebarHideAvatar = useUserPreference('sidebarHideAvatar');
const showRealName = useSetting('UI_Use_Real_Name');
const SideBarItemTemplate = useChatRoomTemplate(sidebarViewMode);
const AvatarTemplate = useAvatarTemplate(sidebarHideAvatar, sidebarViewMode);
const itemSize = itemSizeMap(sidebarViewMode);

const extended = sidebarViewMode === 'extended';

const filterText = useDebouncedValue(filter.value, 100);

const placeholder = [t('Search'), shortcut].filter(Boolean).join(' ');

const { data: items, status } = useSearchItems(filterText);

const itemData = createItemData(items, t, SideBarItemTemplate, AvatarTemplate, showRealName);
const itemData = createItemData(items, t, SideBarItemTemplate, AvatarTemplate, showRealName, extended);

const { ref: boxRef, contentBoxSize: { blockSize = 750 } = {} } = useResizeObserver({ debounceDelay: 100 });

usePreventDefault(boxRef);

const changeSelection = useMutableCallback((dir) => {
let nextSelectedElement = null;

if (dir === 'up') {
nextSelectedElement = selectedElement.current.previousSibling;
} else {
nextSelectedElement = selectedElement.current.nextSibling;
}

if (nextSelectedElement) {
toggleSelectionState(nextSelectedElement, selectedElement.current, autofocus.current);
return nextSelectedElement;
}
return selectedElement.current;
});

const resetCursor = useMutableCallback(() => {
itemIndexRef.current = 0;
listRef.current.scrollToItem(itemIndexRef.current);
selectedElement.current = boxRef.current.querySelector('a.rcx-sidebar-item');
if (selectedElement.current) {
toggleSelectionState(selectedElement.current, undefined, autofocus.current);
}
});

useEffect(() => {
resetCursor();
}, [filterText, resetCursor]);

useEffect(() => {
if (!autofocus.current) {
return;
}
const unsubscribe = tinykeys(autofocus.current, {
Escape: (event) => {
event.preventDefault();
onClose();
setFilterValue((value) => {
if (!value) {
onClose();
}
resetCursor();
return '';
});
},
Tab: onClose,
ArrowUp: () => {
itemIndexRef.current = Math.max(itemIndexRef.current - 1, 0);
listRef.current.scrollToItem(itemIndexRef.current);
const currentElement = changeSelection('up');
selectedElement.current = currentElement;
},
ArrowDown: () => {
const currentElement = changeSelection('down');
selectedElement.current = currentElement;
itemIndexRef.current = Math.min(itemIndexRef.current + 1, items?.length + 1);
listRef.current.scrollToItem(itemIndexRef.current);
},
Enter: () => {
if (selectedElement.current) {
selectedElement.current.click();
}
},
});
return () => {
unsubscribe();
};
}, [autofocus?.current]);
}, [autofocus.current]);

return <Box position='absolute' bg='neutral-200' h='full' display='flex' flexDirection='column' zIndex={99} w='full' className={css`left: 0; top: 0;`} ref={ref}>
<Sidebar.TopBar.Section>
<TextInput data-qa='sidebar-search-input' ref={autofocus} {...filter} placeholder={placeholder} addon={<Icon name='cross' size='x20' onClick={onClose}/>}/>
<Sidebar.TopBar.Section role='search' is='form'>
<TextInput aria-owns={listId} data-qa='sidebar-search-input' ref={autofocus} {...filter} placeholder={placeholder} addon={<Icon name='cross' size='x20' onClick={onClose}/>}/>
</Sidebar.TopBar.Section>
<Box flexShrink={1} h='full' w='full' ref={boxRef} data-qa='sidebar-search-result' onClick={onClose} aria-busy={status !== AsyncState.DONE}>
<Box aria-expanded='true' role='listbox' id={listId} tabIndex={-1} flexShrink={1} h='full' w='full' ref={boxRef} data-qa='sidebar-search-result' onClick={onClose} aria-busy={status !== AsyncState.DONE}>
<List
height={blockSize}
itemCount={items?.length}
itemSize={itemSize}
itemData={itemData}
overscanCount={25}
width='100%'
ref={listRef}
>
{Row}
</List>
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@
"@nivo/pie": "^0.61.1",
"@rocket.chat/apps-engine": "1.18.0",
"@rocket.chat/css-in-js": "^0.16.0",
"@rocket.chat/fuselage": "^0.6.3-dev.103",
"@rocket.chat/fuselage": "^0.6.3-dev.106",
"@rocket.chat/fuselage-hooks": "^0.16.0",
"@rocket.chat/fuselage-polyfills": "^0.16.0",
"@rocket.chat/fuselage-ui-kit": "^0.6.3-dev.103",
"@rocket.chat/fuselage-ui-kit": "^0.6.3-dev.106",
"@rocket.chat/icons": "^0.16.0",
"@rocket.chat/mp3-encoder": "^0.16.0",
"@rocket.chat/ui-kit": "^0.16.0",
Expand Down

0 comments on commit c0b726f

Please sign in to comment.