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
5 changes: 5 additions & 0 deletions .changeset/weak-turtles-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---

Replaces rc-scrollbars in favor of overlayscrollbars fixing some visual and a11y issues when using RTL languages
9 changes: 0 additions & 9 deletions apps/meteor/app/theme/client/imports/general/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,6 @@ button {
height: auto !important /* 1 */;
}
}

.rc-scrollbars-container {
overflow: visible !important; /* 1 */
height: auto !important; /* 1 */
}

.rc-scrollbars-view {
position: relative !important; /* 1 */
}
}

.gallery-item {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/ui-utils/client/lib/RoomHistoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class RoomHistoryManagerClass extends Emitter {
room.oldestTs = messages[messages.length - 1].ts;
}

const wrapper = await waitForElement('.messages-box .wrapper .rc-scrollbars-view');
const wrapper = await waitForElement('.messages-box .wrapper [data-overlayscrollbars-viewport]');

if (wrapper) {
previousHeight = wrapper.scrollHeight;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { css } from '@rocket.chat/css-in-js';
import { Box, Palette } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';
import { forwardRef, memo } from 'react';

import 'overlayscrollbars/styles/overlayscrollbars.css';

export const getScrollbarsOptions = (overflowX?: boolean) =>
({
scrollbars: { autoHide: 'scroll' },
overflow: { x: overflowX ? 'scroll' : 'hidden' },
}) as const;

const BaseScrollbars = forwardRef<HTMLElement, ComponentProps<typeof Box>>(function BaseScrollbars(props, ref) {
return (
<Box
ref={ref}
height='full'
className={css`
.os-scrollbar {
--os-handle-bg: ${Palette.stroke['stroke-dark']};
--os-handle-bg-hover: ${Palette.stroke['stroke-dark']};
--os-handle-bg-active: ${Palette.stroke['stroke-dark']};
}
`}
{...props}
/>
);
});

export default memo(BaseScrollbars);
Original file line number Diff line number Diff line change
@@ -1,61 +1,49 @@
import { Palette } from '@rocket.chat/fuselage';
import type { ScrollValues } from 'rc-scrollbars';
import { Scrollbars } from 'rc-scrollbars';
import type { MutableRefObject, CSSProperties, ReactNode, HTMLAttributes } from 'react';
import { memo, forwardRef, useCallback, useMemo } from 'react';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import type { HTMLAttributes, ReactElement } from 'react';
import { useEffect, useRef, forwardRef, memo } from 'react';

export type CustomScrollbarsProps = {
import type { OverlayScrollbars } from '.';
import BaseScrollbars, { getScrollbarsOptions } from './BaseScrollbars';

type CustomScrollbarsProps = {
children: ReactElement;
overflowX?: boolean;
style?: CSSProperties;
children?: HTMLAttributes<HTMLElement>['children'];
onScroll?: (values: ScrollValues) => void;
renderView?: typeof Scrollbars.defaultProps.renderView;
renderTrackHorizontal?: typeof Scrollbars.defaultProps.renderTrackHorizontal;
autoHide?: boolean;
};

const styleDefault: CSSProperties = {
flexGrow: 1,
overflowY: 'hidden',
};

const CustomScrollbars = forwardRef<HTMLElement, CustomScrollbarsProps>(function CustomScrollbars(
{ children, style, onScroll, overflowX, renderView, ...props },
ref,
) {
const scrollbarsStyle = useMemo(() => ({ ...style, ...styleDefault }), [style]);

const refSetter = useCallback(
(scrollbarRef: Scrollbars | null) => {
if (ref && scrollbarRef) {
if (typeof ref === 'function') {
ref(scrollbarRef.view ?? null);
return;
}

(ref as MutableRefObject<HTMLElement | undefined>).current = scrollbarRef.view;
}
onScroll?: (args: OverlayScrollbars) => void;
} & Omit<HTMLAttributes<HTMLDivElement>, 'is' | 'onScroll'>;

const CustomScrollbars = forwardRef<HTMLElement, CustomScrollbarsProps>(function CustomScrollbars({ overflowX, onScroll, ...props }, ref) {
const rootRef = useRef(null);
const scrollbarsOptions = getScrollbarsOptions(overflowX);
const [initialize, osInstance] = useOverlayScrollbars({
options: scrollbarsOptions,
events: {
scroll: (args) => onScroll?.(args),
},
[ref],
);

return (
<Scrollbars
{...props}
autoHide
autoHideTimeout={2000}
autoHideDuration={500}
style={scrollbarsStyle}
onScrollFrame={onScroll}
renderView={renderView}
renderTrackHorizontal={overflowX ? undefined : (props) => <div {...props} className='track-horizontal' style={{ display: 'none' }} />}
renderThumbVertical={({ style, ...props }) => (
<div {...props} style={{ ...style, backgroundColor: Palette.stroke['stroke-dark'].toString(), borderRadius: '4px' }} />
)}
children={children as ReactNode} // workaround for incompatible types between react-virtuoso and react-i18next
ref={refSetter}
/>
);
});

useEffect(() => {
const { current: root } = rootRef;

if (root) {
initialize(root);

const instance = osInstance();
if (!instance || !ref) {
return;
}

if (typeof ref === 'function') {
ref(instance.elements().viewport || null);
return;
}

ref.current = instance.elements().viewport || null;
}

return () => osInstance()?.destroy();
}, [initialize, osInstance, ref]);

return <BaseScrollbars ref={rootRef} {...props} />;
});

export default memo(CustomScrollbars);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import type { HTMLAttributes, ReactElement } from 'react';
import { useEffect, useState, useRef, cloneElement, forwardRef, memo } from 'react';

import BaseScrollbars, { getScrollbarsOptions } from './BaseScrollbars';

type VirtualizedScrollbarsProps = {
overflowX?: boolean;
children: ReactElement;
} & Omit<HTMLAttributes<HTMLDivElement>, 'is'>;

const VirtualizedScrollbars = forwardRef<HTMLElement, VirtualizedScrollbarsProps>(function VirtualizedScrollbars(
{ overflowX, ...props },
ref,
) {
const rootRef = useRef(null);
const [scroller, setScroller] = useState(null);
const scrollbarsOptions = getScrollbarsOptions(overflowX);
const [initialize, osInstance] = useOverlayScrollbars({
options: scrollbarsOptions,
defer: true,
});

useEffect(() => {
const { current: root } = rootRef;

if (scroller && root) {
initialize({
target: root,
elements: {
viewport: scroller,
},
});
}

return () => osInstance()?.destroy();
}, [initialize, osInstance, ref, scroller]);

return <BaseScrollbars ref={rootRef}>{cloneElement(props.children, { scrollerRef: setScroller })}</BaseScrollbars>;
});

export default memo(VirtualizedScrollbars);

This file was deleted.

6 changes: 4 additions & 2 deletions apps/meteor/client/components/CustomScrollbars/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { OverlayScrollbars } from 'overlayscrollbars';

export { OverlayScrollbars };
export { default as CustomScrollbars } from './CustomScrollbars';
export { default as VirtuosoScrollbars } from './VirtuosoScrollbars';
export * from './CustomScrollbars';
export { default as VirtualizedScrollbars } from './VirtualizedScrollbars';
11 changes: 5 additions & 6 deletions apps/meteor/client/components/Page/PageScrollableContent.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type { Scrollable } from '@rocket.chat/fuselage';
import { Box } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';
import { forwardRef } from 'react';

import type { CustomScrollbarsProps } from '../CustomScrollbars';
import type { OverlayScrollbars } from '../CustomScrollbars';
import { CustomScrollbars } from '../CustomScrollbars';

type PageScrollableContentProps = {
onScrollContent?: ComponentProps<typeof Scrollable>['onScrollContent'];
} & ComponentProps<typeof Box>;
onScroll?: (args: OverlayScrollbars) => void;
} & Omit<ComponentProps<typeof Box>, 'onScroll'>;

const PageScrollableContent = forwardRef<HTMLElement, PageScrollableContentProps>(function PageScrollableContent(
{ onScrollContent, borderBlockEndColor, ...props },
{ onScroll, borderBlockEndColor, ...props },
ref,
) {
return (
Expand All @@ -24,7 +23,7 @@ const PageScrollableContent = forwardRef<HTMLElement, PageScrollableContentProps
overflow='hidden'
borderBlockEndColor={borderBlockEndColor}
>
<CustomScrollbars onScroll={onScrollContent as CustomScrollbarsProps['onScroll']} ref={ref}>
<CustomScrollbars onScroll={onScroll} ref={ref}>
<Box paddingBlock={16} paddingInline={24} display='flex' flexDirection='column' {...props} />
</CustomScrollbars>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import PageScrollableContent from './PageScrollableContent';

type PageScrollableContentWithShadowProps = ComponentPropsWithoutRef<typeof PageScrollableContent>;

const PageScrollableContentWithShadow = ({ onScrollContent, ...props }: PageScrollableContentWithShadowProps) => {
const PageScrollableContentWithShadow = ({ onScroll, ...props }: PageScrollableContentWithShadowProps) => {
const [, setBorder] = useContext(PageContext);
return (
<PageScrollableContent
onScrollContent={({ top, ...args }: { top: boolean }): void => {
onScroll={(args) => {
const top = args.elements().viewport.scrollTop;
setBorder(!!top);
onScrollContent?.({ top, ...args });
onScroll?.(args);
}}
{...props}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ContextualbarInnerContent,
ContextualbarFooter,
} from '../../../../components/Contextualbar';
import { VirtuosoScrollbars } from '../../../../components/CustomScrollbars';
import { VirtualizedScrollbars } from '../../../../components/CustomScrollbars';
import { useRoomToolbox } from '../../../../views/room/contexts/RoomToolboxContext';

type CannedResponseListProps = {
Expand Down Expand Up @@ -92,26 +92,25 @@ const CannedResponseList = ({
{itemCount === 0 && <ContextualbarEmptyContent title={t('No_Canned_Responses')} />}
{itemCount > 0 && cannedItems.length > 0 && (
<Box flexGrow={1} flexShrink={1} overflow='hidden' display='flex'>
<Virtuoso
style={{ width: inlineSize }}
totalCount={itemCount}
endReached={loading ? undefined : (start): void => loadMoreItems(start, Math.min(25, itemCount - start))}
overscan={25}
data={cannedItems}
components={{
Scroller: VirtuosoScrollbars,
}}
itemContent={(_index, data): ReactElement => (
<Item
data={data}
allowUse={!isRoomOverMacLimit}
onClickItem={(): void => {
onClickItem(data);
}}
onClickUse={onClickUse}
/>
)}
/>
<VirtualizedScrollbars>
<Virtuoso
style={{ width: inlineSize }}
totalCount={itemCount}
endReached={loading ? undefined : (start): void => loadMoreItems(start, Math.min(25, itemCount - start))}
overscan={25}
data={cannedItems}
itemContent={(_index, data): ReactElement => (
<Item
data={data}
allowUse={!isRoomOverMacLimit}
onClickItem={(): void => {
onClickItem(data);
}}
onClickUse={onClickUse}
/>
)}
/>
</VirtualizedScrollbars>
</Box>
)}
</ContextualbarContent>
Expand Down
21 changes: 13 additions & 8 deletions apps/meteor/client/sidebar/RoomList/RoomList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Virtuoso } from 'react-virtuoso';
import RoomListRow from './RoomListRow';
import RoomListRowWrapper from './RoomListRowWrapper';
import RoomListWrapper from './RoomListWrapper';
import { VirtuosoScrollbars } from '../../components/CustomScrollbars';
import { VirtualizedScrollbars } from '../../components/CustomScrollbars';
import { useOpenedRoom } from '../../lib/RoomManager';
import { useAvatarTemplate } from '../hooks/useAvatarTemplate';
import { usePreventDefault } from '../hooks/usePreventDefault';
Expand Down Expand Up @@ -121,13 +121,18 @@ const RoomList = (): ReactElement => {
return (
<Box className={[roomsListStyle, 'sidebar--custom-colors'].filter(Boolean)}>
<Box h='full' w='full' ref={ref}>
<Virtuoso
totalCount={roomsList.length}
data={roomsList}
components={{ Item: RoomListRowWrapper, List: RoomListWrapper, Scroller: VirtuosoScrollbars }}
computeItemKey={computeItemKey}
itemContent={(_, data): ReactElement => <RoomListRow data={itemData} item={data} />}
/>
<VirtualizedScrollbars>
<Virtuoso
totalCount={roomsList.length}
data={roomsList}
components={{
Item: RoomListRowWrapper,
List: RoomListWrapper,
}}
computeItemKey={computeItemKey}
itemContent={(_, data): ReactElement => <RoomListRow data={itemData} item={data} />}
/>
</VirtualizedScrollbars>
</Box>
</Box>
);
Expand Down
Loading
Loading