Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/meteor/client/components/ResultsLiveRegion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { VisuallyHidden } from 'react-aria';
import { useTranslation } from 'react-i18next';

const ResultsLiveRegion = ({ shouldAnnounce, itemCount }: { shouldAnnounce: boolean; itemCount: number }) => {
const { t } = useTranslation();

if (itemCount === 0) {
return <VisuallyHidden role='status'>{shouldAnnounce && t('No_results_found')}</VisuallyHidden>;
}

return <VisuallyHidden role='status'>{shouldAnnounce && t('__count__result_found', { count: itemCount })}</VisuallyHidden>;
};

export default ResultsLiveRegion;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { composeStories } from '@storybook/react';
import { render } from '@testing-library/react';
import { axe } from 'jest-axe';

import * as stories from './DiscussionsList.stories';

jest.mock('../../../../lib/rooms/roomCoordinator', () => ({
roomCoordinator: {
getRoomDirectives: jest.fn(() => ({})),
},
}));

const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]);
test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => {
const { baseElement } = render(<Story />);
expect(baseElement).toMatchSnapshot();
});

test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => {
const { container } = render(<Story />);

const results = await axe(container);
expect(results).toHaveNoViolations();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Contextualbar } from '@rocket.chat/ui-client';
import { action } from '@storybook/addon-actions';
import type { Meta, StoryFn } from '@storybook/react';

import DiscussionsList from './DiscussionsList';

export default {
component: DiscussionsList,
parameters: {
layout: 'fullscreen',
actions: { argTypesRegex: '^on.*' },
},
decorators: [(fn) => <Contextualbar height='100vh'>{fn()}</Contextualbar>],
args: {
text: '',
loadMoreItems: action('loadMoreItems'),
},
} satisfies Meta<typeof DiscussionsList>;

const Template: StoryFn<typeof DiscussionsList> = (args) => <DiscussionsList {...args} />;

const fakeDiscussions = Array.from({ length: 10 }, (_, i) => ({
_id: String(i),
msg: `Discussion ${i}`,
ts: new Date('2024-01-01T00:00:00Z'),
username: 'user.name',
dcount: 5,
dlm: new Date('2024-01-01T00:00:00Z'),
drid: `drid-${i}`,
rid: 'roomId',
_updatedAt: new Date('2024-01-01T00:00:00Z'),
u: {
_id: 'user-id',
username: 'user.name',
},
}));

export const Default = Template.bind({});
Default.args = {
isSuccess: true,
discussions: fakeDiscussions,
itemCount: fakeDiscussions.length,
};

export const Loading = Template.bind({});
Loading.args = {
isPending: true,
};

export const Empty = Template.bind({});
Empty.args = {
isSuccess: true,
discussions: [],
itemCount: 0,
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,40 @@ import {
} from '@rocket.chat/ui-client';
import { useSetting } from '@rocket.chat/ui-contexts';
import type { ChangeEvent, MouseEvent, RefObject } from 'react';
import { useCallback } from 'react';
import { useCallback, useId } from 'react';
import { useTranslation } from 'react-i18next';
import { Virtuoso } from 'react-virtuoso';

import DiscussionsListRow from './DiscussionsListRow';
import ResultsLiveRegion from '../../../../components/ResultsLiveRegion';
import { useGoToRoom } from '../../hooks/useGoToRoom';

type DiscussionsListProps = {
total: number;
itemCount: number;
discussions: Array<IDiscussionMessage>;
loadMoreItems: (start: number, end: number) => void;
loading: boolean;
isPending: boolean;
isSuccess: boolean;
onClose: () => void;
error: unknown;
text: string;
onChangeFilter: (e: ChangeEvent<HTMLInputElement>) => void;
};

function DiscussionsList({
total = 10,
itemCount,
discussions = [],
loadMoreItems,
loading,
isPending,
isSuccess,
onClose,
error,
text,
onChangeFilter,
}: DiscussionsListProps) {
const { t } = useTranslation();
const discussionListId = useId();

const showRealNames = useSetting('UI_Use_Real_Name', false);
const inputRef = useAutoFocus(true);

Expand Down Expand Up @@ -70,44 +75,46 @@ function DiscussionsList({
<ContextualbarSection>
<TextInput
placeholder={t('Search_Messages')}
aria-label={t('Search_Messages')}
aria-controls={isSuccess ? discussionListId : undefined}
value={text}
onChange={onChangeFilter}
ref={inputRef as RefObject<HTMLInputElement>}
addon={<Icon name='magnifier' size='x20' />}
/>
</ContextualbarSection>
<ContextualbarContent paddingInline={0} ref={ref}>
{loading && (
<ResultsLiveRegion shouldAnnounce={isSuccess} itemCount={itemCount} />
{isPending && (
<Box pi={24} pb={12}>
<Throbber size='x12' />
</Box>
)}

{error instanceof Error && (
<Callout mi={24} type='danger'>
{error.toString()}
</Callout>
)}

{!loading && total === 0 && <ContextualbarEmptyContent title={t('No_Discussions_found')} />}

<Box flexGrow={1} flexShrink={1} overflow='hidden' display='flex'>
{!error && total > 0 && discussions.length > 0 && (
<VirtualizedScrollbars>
<Virtuoso
style={{
height: blockSize,
width: inlineSize,
}}
totalCount={total}
endReached={loading ? () => undefined : (start) => loadMoreItems(start, Math.min(50, total - start))}
overscan={25}
data={discussions}
itemContent={(_, data) => <DiscussionsListRow discussion={data} showRealNames={showRealNames} onClick={onClick} />}
/>
</VirtualizedScrollbars>
)}
</Box>
{isSuccess && (
<Box id={discussionListId} w='full' h='full' overflow='hidden' flexShrink={1}>
{discussions.length === 0 && <ContextualbarEmptyContent title={t('No_Discussions_found')} />}
{discussions.length > 0 && (
<VirtualizedScrollbars>
<Virtuoso
style={{
height: blockSize,
width: inlineSize,
}}
totalCount={itemCount}
endReached={isPending ? () => undefined : (start) => loadMoreItems(start, Math.min(50, itemCount - start))}
overscan={25}
data={discussions}
itemContent={(_, data) => <DiscussionsListRow discussion={data} showRealNames={showRealNames} onClick={onClick} />}
/>
</VirtualizedScrollbars>
)}
</Box>
)}
</ContextualbarContent>
</ContextualbarDialog>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ const DiscussionListContextBar = () => {
[room._id, debouncedText],
);

const { isPending, error, data, fetchNextPage } = useDiscussionsList(options);
const { isPending, isSuccess, error, data, fetchNextPage } = useDiscussionsList(options);

const discussions = data?.items || [];
const totalItemCount = data?.itemCount ?? 0;
const itemCount = data?.itemCount ?? 0;

const handleTextChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setText(e.currentTarget.value);
Expand All @@ -41,8 +41,9 @@ const DiscussionListContextBar = () => {
onClose={closeTab}
error={error}
discussions={discussions}
total={totalItemCount}
loading={isPending}
itemCount={itemCount}
isPending={isPending}
isSuccess={isSuccess}
loadMoreItems={() => fetchNextPage()}
text={text}
onChangeFilter={handleTextChange}
Expand Down
Loading
Loading