Skip to content

Commit

Permalink
refactor: ♻️ Improve UsersTableFilters and MultiSelectCustom components
Browse files Browse the repository at this point in the history
Changed the implementation of the new users table filters from a fully custom "Box based" code to a standardized use of the FilterByText component with children. Also improved the positioning and styling of the MultiSelectCustom by properly implementing the useOutsideClick and usePosition custom hooks together with reusing the fuselage button and input styles in the anchor of the component.
  • Loading branch information
rique223 committed Apr 23, 2024
1 parent 9b6edb4 commit 086b806
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { IRole } from '@rocket.chat/core-typings';
import { Box, Icon, TextInput } from '@rocket.chat/fuselage';
import { useBreakpoints } from '@rocket.chat/fuselage-hooks';
import type { OptionProp } from '@rocket.chat/ui-client';
import { MultiSelectCustom } from '@rocket.chat/ui-client';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

import FilterByText from '../../../../components/FilterByText';
import type { UsersFilters } from '../AdminUsersPage';

type UsersTableFiltersProps = {
Expand All @@ -19,8 +20,7 @@ const UsersTableFilters = ({ roleData, setUsersFilters }: UsersTableFiltersProps
const [text, setText] = useState('');

const handleSearchTextChange = useCallback(
(event) => {
const text = event.currentTarget.value;
({ text }) => {
setUsersFilters({ text, roles: selectedRoles });
setText(text);
},
Expand Down Expand Up @@ -57,37 +57,21 @@ const UsersTableFilters = ({ roleData, setUsersFilters }: UsersTableFiltersProps
[roleData],
);

const breakpoints = useBreakpoints();
const fixFiltersSize = breakpoints.includes('lg') ? { maxWidth: 'x200', minWidth: 'x200' } : null;

return (
<Box
is='form'
onSubmit={useCallback((e) => e.preventDefault(), [])}
mb='x8'
display='flex'
flexWrap='wrap'
alignItems='center'
justifyContent='center'
>
<Box minWidth='x224' display='flex' m='x4' flexGrow={2}>
<TextInput
name='Search_Users'
alignItems='center'
placeholder={t('Search_Users')}
addon={<Icon name='magnifier' size='x20' />}
onChange={handleSearchTextChange}
value={text}
/>
</Box>
<Box minWidth='x224' m='x4'>
<MultiSelectCustom
dropdownOptions={userRolesFilterStructure}
defaultTitle='All_roles'
selectedOptionsTitle='Roles'
setSelectedOptions={handleRolesChange}
selectedOptions={selectedRoles}
searchBarText='Search_roles'
/>
</Box>
</Box>
<FilterByText shouldAutoFocus placeholder={t('Search_Users')} onChange={handleSearchTextChange}>
<MultiSelectCustom
dropdownOptions={userRolesFilterStructure}
defaultTitle='All_roles'
selectedOptionsTitle='Roles'
setSelectedOptions={handleRolesChange}
selectedOptions={selectedRoles}
searchBarText='Search_roles'
{...fixFiltersSize}
/>
</FilterByText>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box } from '@rocket.chat/fuselage';
import { useOutsideClick, useToggle } from '@rocket.chat/fuselage-hooks';
import { Button } from '@rocket.chat/fuselage';
import { useToggle } from '@rocket.chat/fuselage-hooks';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import type { FormEvent, ReactElement, RefObject } from 'react';
import type { ComponentProps, FormEvent, ReactElement, RefObject } from 'react';
import { useCallback, useRef } from 'react';

import MultiSelectCustomAnchor from './MultiSelectCustomAnchor';
Expand Down Expand Up @@ -48,7 +48,7 @@ type DropDownProps = {
selectedOptions: OptionProp[];
setSelectedOptions: (roles: OptionProp[]) => void;
searchBarText?: TranslationKey;
};
} & ComponentProps<typeof Button>;

export const MultiSelectCustom = ({
dropdownOptions,
Expand All @@ -57,9 +57,9 @@ export const MultiSelectCustom = ({
selectedOptions,
setSelectedOptions,
searchBarText,
...props
}: DropDownProps): ReactElement => {
const reference = useRef<HTMLInputElement>(null);
const target = useRef<HTMLElement>(null);
const [collapsed, toggleCollapsed] = useToggle(false);

const onClose = useCallback(
Expand All @@ -74,8 +74,6 @@ export const MultiSelectCustom = ({
[toggleCollapsed],
);

useOutsideClick([target], onClose);

const onSelect = (item: OptionProp, e?: FormEvent<HTMLElement>): void => {
e?.stopPropagation();
item.checked = !item.checked;
Expand All @@ -92,21 +90,21 @@ export const MultiSelectCustom = ({
const count = dropdownOptions.filter((option) => option.checked).length;

return (
<Box display='flex' flexGrow={1} position='relative'>
<>
<MultiSelectCustomAnchor
ref={reference}
onClick={toggleCollapsed as any}
collapsed={collapsed}
defaultTitle={defaultTitle}
selectedOptionsTitle={selectedOptionsTitle}
selectedOptionsCount={count}
maxCount={dropdownOptions.length}
{...props}
/>
{collapsed && (
<MultiSelectCustomListWrapper ref={target}>
<MultiSelectCustomListWrapper ref={reference} onClose={onClose}>
<MultiSelectCustomList options={dropdownOptions} onSelected={onSelect} searchBarText={searchBarText} />
</MultiSelectCustomListWrapper>
)}
</Box>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { css } from '@rocket.chat/css-in-js';
import { Box, Button, Icon, Palette } from '@rocket.chat/fuselage';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
Expand All @@ -7,40 +6,23 @@ import { forwardRef } from 'react';

type MultiSelectCustomAnchorProps = {
onClick?: (value: boolean) => void;
collapsed: boolean;
defaultTitle: TranslationKey;
selectedOptionsTitle: TranslationKey;
selectedOptionsCount: number;
maxCount: number;
} & ComponentProps<typeof Button>;

const MultiSelectCustomAnchor = forwardRef<HTMLElement, MultiSelectCustomAnchorProps>(function MultiSelectCustomAnchor(
{ onClick, collapsed, selectedOptionsCount, selectedOptionsTitle, defaultTitle, maxCount, ...props },
{ onClick, selectedOptionsCount, selectedOptionsTitle, defaultTitle, maxCount, ...props },
ref,
) {
const t = useTranslation();

const inputStyle = collapsed
? css`
&,
&:hover,
&:active,
&:focus {
cursor: pointer;
border-color: ${Palette.stroke['stroke-highlight'].toString()}!important;
box-shadow: 0 0 0 2px ${Palette.shadow['shadow-highlight'].toString()};
}
`
: css`
& {
cursor: pointer;
}
`;

const isDirty = selectedOptionsCount > 0 && selectedOptionsCount !== maxCount - 1;

return (
<Box
is='button'
ref={ref}
onClick={onClick}
display='flex'
Expand All @@ -56,7 +38,7 @@ const MultiSelectCustomAnchor = forwardRef<HTMLElement, MultiSelectCustomAnchorP
pb={10}
pi={16}
color={isDirty ? Palette.text['font-default'].toString() : Palette.text['font-annotation'].toString()}
className={inputStyle}
rcx-input-box
{...props}
>
{isDirty ? `${t(selectedOptionsTitle)} (${selectedOptionsCount})` : t(defaultTitle)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import { Box } from '@rocket.chat/fuselage';
import { forwardRef, type ComponentProps } from 'react';

const MultiSelectCustomListWrapper = forwardRef<Element, ComponentProps<typeof Box>>(function MultiSelectCustomListWrapper(
{ children },
ref,
) {
return (
<Box ref={ref} zIndex='2' w='full' position='absolute' mbs={40} pbs={4}>
{children}
</Box>
);
});
import { useOutsideClick, usePosition } from '@rocket.chat/fuselage-hooks';
import { forwardRef, useRef, type ComponentProps } from 'react';

const options = {
margin: 8,
placement: 'bottom-end',
} as const;

const hidden = {
visibility: 'hidden',
opacity: 0,
position: 'fixed',
} as const;

const MultiSelectCustomListWrapper = forwardRef<Element, ComponentProps<typeof Box> & { onClose: (e: MouseEvent) => void }>(
function MultiSelectCustomListWrapper({ children, onClose }, ref) {
const target = useRef<HTMLElement>(null);
useOutsideClick([target], onClose);
const { style = hidden } = usePosition(ref as Parameters<typeof usePosition>[0], target, options);
return (
<Box ref={target} style={style} minWidth={224} zIndex='99999'>
{children}
</Box>
);
},
);

export default MultiSelectCustomListWrapper;

0 comments on commit 086b806

Please sign in to comment.