Shared:Components:Selector: update types for union
This commit is contained in:
parent
943f0944de
commit
253d82b929
@ -5,16 +5,26 @@ import { Body } from "./sub-components/Body";
|
||||
import { Footer } from "./sub-components/Footer";
|
||||
|
||||
import { StyledSelector } from "./Selector.styled";
|
||||
import { AccessRight, SelectorProps, TSelectorItem } from "./Selector.types";
|
||||
import {
|
||||
TAccessRight,
|
||||
SelectorProps,
|
||||
TSelectorBodySearch,
|
||||
TSelectorBreadCrumbs,
|
||||
TSelectorCancelButton,
|
||||
TSelectorItem,
|
||||
TSelectorSelectAll,
|
||||
TSelectorAccessRights,
|
||||
TSelectorFooterInput,
|
||||
TSelectorFooterCheckbox,
|
||||
} from "./Selector.types";
|
||||
|
||||
const Selector = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
|
||||
headerLabel,
|
||||
withoutBackButton,
|
||||
onBackClick,
|
||||
withHeader,
|
||||
headerProps,
|
||||
|
||||
isBreadCrumbsLoading,
|
||||
breadCrumbsLoader,
|
||||
@ -35,31 +45,44 @@ const Selector = ({
|
||||
selectAllIcon,
|
||||
onSelectAll,
|
||||
|
||||
items,
|
||||
renderCustomItem,
|
||||
isMultiSelect,
|
||||
selectedItems,
|
||||
acceptButtonLabel,
|
||||
onSelect,
|
||||
onAccept,
|
||||
rowLoader,
|
||||
emptyScreenImage,
|
||||
emptyScreenHeader,
|
||||
emptyScreenDescription,
|
||||
searchEmptyScreenImage,
|
||||
searchEmptyScreenHeader,
|
||||
searchEmptyScreenDescription,
|
||||
|
||||
submitButtonLabel,
|
||||
submitButtonId,
|
||||
disableSubmitButton,
|
||||
onSubmit,
|
||||
|
||||
withCancelButton,
|
||||
cancelButtonLabel,
|
||||
onCancel,
|
||||
|
||||
withAccessRights,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccessRightsChange,
|
||||
|
||||
withCancelButton,
|
||||
cancelButtonLabel,
|
||||
onCancel,
|
||||
withFooterInput,
|
||||
footerInputHeader,
|
||||
currentFooterInputValue,
|
||||
|
||||
emptyScreenImage,
|
||||
emptyScreenHeader,
|
||||
emptyScreenDescription,
|
||||
withFooterCheckbox,
|
||||
footerCheckboxLabel,
|
||||
isChecked,
|
||||
setIsChecked,
|
||||
|
||||
searchEmptyScreenImage,
|
||||
searchEmptyScreenHeader,
|
||||
searchEmptyScreenDescription,
|
||||
items,
|
||||
renderCustomItem,
|
||||
isMultiSelect,
|
||||
selectedItems,
|
||||
|
||||
onSelect,
|
||||
|
||||
rowLoader,
|
||||
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
@ -67,22 +90,11 @@ const Selector = ({
|
||||
totalItems,
|
||||
isLoading,
|
||||
|
||||
withHeader,
|
||||
|
||||
withFooterInput,
|
||||
withFooterCheckbox,
|
||||
footerInputHeader,
|
||||
footerCheckboxLabel,
|
||||
currentFooterInputValue,
|
||||
|
||||
alwaysShowFooter,
|
||||
disableAcceptButton,
|
||||
|
||||
descriptionText,
|
||||
acceptButtonId,
|
||||
|
||||
cancelButtonId,
|
||||
isChecked,
|
||||
setIsChecked,
|
||||
}: SelectorProps) => {
|
||||
const [footerVisible, setFooterVisible] = React.useState<boolean>(false);
|
||||
const [isSearch, setIsSearch] = React.useState<boolean>(false);
|
||||
@ -99,26 +111,7 @@ const Selector = ({
|
||||
React.useState<boolean>(isChecked || false);
|
||||
|
||||
const [selectedAccess, setSelectedAccess] =
|
||||
React.useState<AccessRight | null>(null);
|
||||
|
||||
const onBackClickAction = React.useCallback(() => {
|
||||
onBackClick?.();
|
||||
}, [onBackClick]);
|
||||
|
||||
const onClearSearchAction = React.useCallback(() => {
|
||||
onClearSearch?.(() => setIsSearch(false));
|
||||
}, [onClearSearch]);
|
||||
|
||||
const onSearchAction = React.useCallback(
|
||||
(value: string) => {
|
||||
const v = value.trim();
|
||||
|
||||
if (v === "") return onClearSearchAction();
|
||||
|
||||
onSearch?.(v, () => setIsSearch(true));
|
||||
},
|
||||
[onSearch, onClearSearchAction],
|
||||
);
|
||||
React.useState<TAccessRight | null>(null);
|
||||
|
||||
const compareSelectedItems = React.useCallback(
|
||||
(newList: TSelectorItem[]) => {
|
||||
@ -144,12 +137,6 @@ const Selector = ({
|
||||
const onSelectAction = (item: TSelectorItem) => {
|
||||
onSelect?.({
|
||||
...item,
|
||||
id: item.id,
|
||||
email: item.email || "",
|
||||
avatar: item.avatar,
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
shared: item.shared,
|
||||
});
|
||||
|
||||
if (isMultiSelect) {
|
||||
@ -176,8 +163,6 @@ const Selector = ({
|
||||
} else {
|
||||
setNewSelectedItems((value) => {
|
||||
value.push({
|
||||
id: item.id,
|
||||
email: item.email,
|
||||
...item,
|
||||
});
|
||||
|
||||
@ -203,9 +188,6 @@ const Selector = ({
|
||||
});
|
||||
|
||||
const newItem = {
|
||||
id: item.id,
|
||||
email: item.email,
|
||||
|
||||
...item,
|
||||
};
|
||||
|
||||
@ -247,8 +229,8 @@ const Selector = ({
|
||||
}
|
||||
}, [compareSelectedItems, items, newSelectedItems.length, onSelectAll]);
|
||||
|
||||
const onAcceptAction = () => {
|
||||
onAccept?.(
|
||||
const onSubmitAction = () => {
|
||||
onSubmit(
|
||||
newSelectedItems,
|
||||
selectedAccess,
|
||||
newFooterInputValue,
|
||||
@ -256,12 +238,8 @@ const Selector = ({
|
||||
);
|
||||
};
|
||||
|
||||
const onCancelAction = React.useCallback(() => {
|
||||
onCancel?.();
|
||||
}, [onCancel]);
|
||||
|
||||
const onChangeAccessRightsAction = React.useCallback(
|
||||
(access: AccessRight) => {
|
||||
(access: TAccessRight) => {
|
||||
setSelectedAccess({ ...access });
|
||||
onAccessRightsChange?.(access);
|
||||
},
|
||||
@ -304,6 +282,94 @@ const Selector = ({
|
||||
compareSelectedItems(cloneSelectedItems);
|
||||
}
|
||||
}, [items, selectedItems, isMultiSelect, compareSelectedItems]);
|
||||
|
||||
const breadCrumbsProps: TSelectorBreadCrumbs = withBreadCrumbs
|
||||
? {
|
||||
withBreadCrumbs,
|
||||
breadCrumbs,
|
||||
onSelectBreadCrumb,
|
||||
breadCrumbsLoader,
|
||||
isBreadCrumbsLoading,
|
||||
}
|
||||
: ({} as TSelectorBreadCrumbs);
|
||||
|
||||
const onSelectAllProps: TSelectorSelectAll = withSelectAll
|
||||
? {
|
||||
withSelectAll,
|
||||
selectAllLabel,
|
||||
selectAllIcon,
|
||||
onSelectAll: onSelectAllAction,
|
||||
}
|
||||
: ({} as TSelectorSelectAll);
|
||||
|
||||
const searchProps: TSelectorBodySearch = withSearch
|
||||
? {
|
||||
withSearch,
|
||||
searchPlaceholder,
|
||||
searchLoader,
|
||||
isSearchLoading,
|
||||
searchValue,
|
||||
setIsSearch,
|
||||
onClearSearch,
|
||||
isSearch,
|
||||
onSearch,
|
||||
isAllIndeterminate:
|
||||
newSelectedItems.length !== renderedItems.length &&
|
||||
newSelectedItems.length !== 0,
|
||||
isAllChecked:
|
||||
newSelectedItems.length === renderedItems.length &&
|
||||
renderedItems.length !== 0,
|
||||
}
|
||||
: ({
|
||||
isSearch,
|
||||
setIsSearch,
|
||||
isAllIndeterminate:
|
||||
newSelectedItems.length !== renderedItems.length &&
|
||||
newSelectedItems.length !== 0,
|
||||
isAllChecked:
|
||||
newSelectedItems.length === renderedItems.length &&
|
||||
renderedItems.length !== 0,
|
||||
} as TSelectorBodySearch);
|
||||
|
||||
const cancelButtonProps = withCancelButton
|
||||
? { withCancelButton, onCancel, cancelButtonLabel, cancelButtonId }
|
||||
: ({} as TSelectorCancelButton);
|
||||
|
||||
const accessRightsProps = withAccessRights
|
||||
? {
|
||||
withAccessRights,
|
||||
accessRights,
|
||||
selectedAccessRight: selectedAccess,
|
||||
onAccessRightsChange: onChangeAccessRightsAction,
|
||||
}
|
||||
: ({} as TSelectorAccessRights);
|
||||
|
||||
const inputProps = withFooterInput
|
||||
? {
|
||||
withFooterInput,
|
||||
footerInputHeader,
|
||||
currentFooterInputValue: newFooterInputValue,
|
||||
setNewFooterInputValue,
|
||||
}
|
||||
: ({
|
||||
currentFooterInputValue: newFooterInputValue,
|
||||
setNewFooterInputValue,
|
||||
} as TSelectorFooterInput);
|
||||
|
||||
const checkboxProps: TSelectorFooterCheckbox = withFooterCheckbox
|
||||
? {
|
||||
withFooterCheckbox,
|
||||
footerCheckboxLabel,
|
||||
isChecked: isFooterCheckboxChecked,
|
||||
setIsFooterCheckboxChecked,
|
||||
setIsChecked,
|
||||
}
|
||||
: ({
|
||||
isChecked: isFooterCheckboxChecked,
|
||||
setIsFooterCheckboxChecked,
|
||||
setIsChecked,
|
||||
} as TSelectorFooterCheckbox);
|
||||
|
||||
return (
|
||||
<StyledSelector
|
||||
id={id}
|
||||
@ -311,37 +377,15 @@ const Selector = ({
|
||||
style={style}
|
||||
data-testid="selector"
|
||||
>
|
||||
{withHeader && (
|
||||
<Header
|
||||
onBackClickAction={onBackClickAction}
|
||||
headerLabel={headerLabel}
|
||||
withoutBackButton={withoutBackButton}
|
||||
/>
|
||||
)}
|
||||
{withHeader && <Header {...headerProps} />}
|
||||
|
||||
<Body
|
||||
withHeader={withHeader}
|
||||
footerVisible={footerVisible || !!alwaysShowFooter}
|
||||
isSearch={isSearch}
|
||||
isAllIndeterminate={
|
||||
newSelectedItems.length !== renderedItems.length &&
|
||||
newSelectedItems.length !== 0
|
||||
}
|
||||
isAllChecked={
|
||||
newSelectedItems.length === renderedItems.length &&
|
||||
renderedItems.length !== 0
|
||||
}
|
||||
placeholder={searchPlaceholder}
|
||||
value={searchValue}
|
||||
onSearch={onSearchAction}
|
||||
onClearSearch={onClearSearchAction}
|
||||
items={renderedItems}
|
||||
isMultiSelect={isMultiSelect}
|
||||
onSelect={onSelectAction}
|
||||
withSelectAll={withSelectAll}
|
||||
selectAllLabel={selectAllLabel}
|
||||
selectAllIcon={selectAllIcon}
|
||||
onSelectAll={onSelectAllAction}
|
||||
// empty screen
|
||||
emptyScreenImage={emptyScreenImage}
|
||||
emptyScreenHeader={emptyScreenHeader}
|
||||
emptyScreenDescription={emptyScreenDescription}
|
||||
@ -354,49 +398,34 @@ const Selector = ({
|
||||
renderCustomItem={renderCustomItem}
|
||||
totalItems={totalItems || 0}
|
||||
isLoading={isLoading}
|
||||
searchLoader={searchLoader}
|
||||
rowLoader={rowLoader}
|
||||
withBreadCrumbs={withBreadCrumbs}
|
||||
breadCrumbs={breadCrumbs}
|
||||
onSelectBreadCrumb={onSelectBreadCrumb}
|
||||
breadCrumbsLoader={breadCrumbsLoader}
|
||||
isBreadCrumbsLoading={isBreadCrumbsLoading}
|
||||
isSearchLoading={isSearchLoading}
|
||||
withSearch={withSearch}
|
||||
withFooterInput={withFooterInput}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
descriptionText={descriptionText}
|
||||
// bread crumbs
|
||||
{...breadCrumbsProps}
|
||||
// select all
|
||||
{...onSelectAllProps}
|
||||
// search
|
||||
{...searchProps}
|
||||
/>
|
||||
|
||||
{(footerVisible || alwaysShowFooter) && (
|
||||
<Footer
|
||||
isMultiSelect={isMultiSelect}
|
||||
acceptButtonLabel={acceptButtonLabel || ""}
|
||||
selectedItemsCount={newSelectedItems.length}
|
||||
withCancelButton={withCancelButton}
|
||||
cancelButtonLabel={cancelButtonLabel}
|
||||
withAccessRights={withAccessRights}
|
||||
accessRights={accessRights}
|
||||
selectedAccessRight={selectedAccess}
|
||||
onAccept={onAcceptAction}
|
||||
onCancel={onCancelAction}
|
||||
onChangeAccessRights={onChangeAccessRightsAction}
|
||||
withFooterInput={withFooterInput}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
footerInputHeader={footerInputHeader}
|
||||
footerCheckboxLabel={footerCheckboxLabel}
|
||||
currentFooterInputValue={newFooterInputValue}
|
||||
setNewFooterInputValue={setNewFooterInputValue}
|
||||
isFooterCheckboxChecked={isFooterCheckboxChecked}
|
||||
setIsFooterCheckboxChecked={setIsFooterCheckboxChecked}
|
||||
setIsChecked={setIsChecked}
|
||||
disableAcceptButton={
|
||||
withFooterInput
|
||||
? disableAcceptButton
|
||||
: disableAcceptButton && !newFooterInputValue.trim()
|
||||
}
|
||||
acceptButtonId={acceptButtonId}
|
||||
cancelButtonId={cancelButtonId}
|
||||
onSubmit={onSubmitAction}
|
||||
submitButtonLabel={submitButtonLabel}
|
||||
disableSubmitButton={disableSubmitButton}
|
||||
submitButtonId={submitButtonId}
|
||||
// cancel button
|
||||
{...cancelButtonProps}
|
||||
// access rights
|
||||
{...accessRightsProps}
|
||||
// input
|
||||
{...inputProps}
|
||||
// checkbox
|
||||
{...checkboxProps}
|
||||
/>
|
||||
)}
|
||||
</StyledSelector>
|
||||
|
@ -2,191 +2,367 @@ import React from "react";
|
||||
import { RoomsType } from "../../enums";
|
||||
import { AvatarRole } from "../avatar";
|
||||
|
||||
export type AccessRight = {
|
||||
// header
|
||||
|
||||
type THeaderBackButton = {
|
||||
onBackClick: () => void;
|
||||
withoutBackButton: false;
|
||||
};
|
||||
|
||||
type THeaderNonBackButton = {
|
||||
onBackClick?: undefined;
|
||||
withoutBackButton?: undefined;
|
||||
};
|
||||
|
||||
export type HeaderProps = {
|
||||
headerLabel: string;
|
||||
} & (THeaderBackButton | THeaderNonBackButton);
|
||||
|
||||
type TSelectorHeader =
|
||||
| {
|
||||
withHeader: true;
|
||||
headerProps: HeaderProps;
|
||||
}
|
||||
| { withHeader?: undefined; headerProps?: undefined };
|
||||
|
||||
// bread crumbs
|
||||
|
||||
export type TBreadCrumb = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
isRoom?: boolean;
|
||||
minWidth?: string;
|
||||
onClick?: (e: React.MouseEvent, open: boolean, item: TBreadCrumb) => void;
|
||||
};
|
||||
|
||||
export interface BreadCrumbsProps {
|
||||
breadCrumbs: TBreadCrumb[];
|
||||
onSelectBreadCrumb: (item: TBreadCrumb) => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export type TSelectorBreadCrumbs =
|
||||
| {
|
||||
withBreadCrumbs: true;
|
||||
breadCrumbs: TBreadCrumb[];
|
||||
onSelectBreadCrumb: (item: TBreadCrumb) => void;
|
||||
breadCrumbsLoader: React.ReactNode;
|
||||
isBreadCrumbsLoading: boolean;
|
||||
}
|
||||
| {
|
||||
withBreadCrumbs?: undefined;
|
||||
breadCrumbs?: undefined;
|
||||
onSelectBreadCrumb?: undefined;
|
||||
breadCrumbsLoader?: undefined;
|
||||
isBreadCrumbsLoading?: undefined;
|
||||
};
|
||||
|
||||
// select all
|
||||
|
||||
export interface SelectAllProps {
|
||||
label: string;
|
||||
icon: string;
|
||||
onSelectAll: () => void;
|
||||
isChecked: boolean;
|
||||
isIndeterminate: boolean;
|
||||
isLoading: boolean;
|
||||
rowLoader: React.ReactNode;
|
||||
}
|
||||
|
||||
export type TSelectorSelectAll =
|
||||
| {
|
||||
withSelectAll: true;
|
||||
selectAllLabel: string;
|
||||
selectAllIcon: string;
|
||||
onSelectAll: () => void;
|
||||
}
|
||||
| {
|
||||
withSelectAll?: undefined;
|
||||
selectAllLabel?: undefined;
|
||||
selectAllIcon?: undefined;
|
||||
onSelectAll?: undefined;
|
||||
};
|
||||
|
||||
// search
|
||||
export interface SearchProps {
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
onSearch: (value: string, callback?: Function) => void;
|
||||
onClearSearch: (callback?: Function) => void;
|
||||
setIsSearch: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
type TSelectorSearch =
|
||||
| {
|
||||
withSearch: true;
|
||||
searchLoader: React.ReactNode;
|
||||
isSearchLoading: boolean;
|
||||
searchPlaceholder?: string;
|
||||
searchValue?: string;
|
||||
onSearch: (value: string, callback?: Function) => void;
|
||||
onClearSearch: (callback?: Function) => void;
|
||||
}
|
||||
| {
|
||||
withSearch?: undefined;
|
||||
searchLoader?: undefined;
|
||||
isSearchLoading?: undefined;
|
||||
searchPlaceholder?: string;
|
||||
searchValue?: string;
|
||||
onSearch?: undefined;
|
||||
onClearSearch?: undefined;
|
||||
};
|
||||
|
||||
export type TSelectorBodySearch = TSelectorSearch & {
|
||||
isSearch: boolean;
|
||||
setIsSearch: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isAllIndeterminate: boolean;
|
||||
isAllChecked: boolean;
|
||||
};
|
||||
|
||||
// empty screen
|
||||
export interface EmptyScreenProps {
|
||||
image: string;
|
||||
header: string;
|
||||
description: string;
|
||||
searchImage: string;
|
||||
searchHeader: string;
|
||||
searchDescription: string;
|
||||
withSearch: boolean;
|
||||
}
|
||||
|
||||
type TSelectorEmptyScreen = {
|
||||
emptyScreenImage: string;
|
||||
emptyScreenHeader: string;
|
||||
emptyScreenDescription: string;
|
||||
|
||||
searchEmptyScreenImage: string;
|
||||
searchEmptyScreenHeader: string;
|
||||
searchEmptyScreenDescription: string;
|
||||
};
|
||||
|
||||
// submit button
|
||||
type TSelectorSubmitButton = {
|
||||
submitButtonLabel: string;
|
||||
disableSubmitButton: boolean;
|
||||
onSubmit: (
|
||||
selectedItems: TSelectorItem[],
|
||||
access: TAccessRight | null,
|
||||
fileName: string,
|
||||
isFooterCheckboxChecked: boolean,
|
||||
) => void;
|
||||
submitButtonId?: string;
|
||||
};
|
||||
|
||||
type TSelectorFooterSubmitButton = TSelectorSubmitButton & {
|
||||
onSubmit: () => void;
|
||||
};
|
||||
|
||||
// cancel button
|
||||
|
||||
export type TSelectorCancelButton =
|
||||
| {
|
||||
withCancelButton: true;
|
||||
cancelButtonLabel: string;
|
||||
onCancel: () => void;
|
||||
cancelButtonId?: string;
|
||||
}
|
||||
| {
|
||||
withCancelButton?: undefined;
|
||||
cancelButtonLabel?: undefined;
|
||||
onCancel?: undefined;
|
||||
cancelButtonId?: undefined;
|
||||
};
|
||||
|
||||
// access rights
|
||||
|
||||
export type TAccessRight = {
|
||||
key: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
access: string | number;
|
||||
};
|
||||
|
||||
export interface SelectorProps {
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
withHeader?: boolean;
|
||||
headerLabel: string;
|
||||
withoutBackButton?: boolean;
|
||||
onBackClick?: () => void;
|
||||
withSearch?: boolean;
|
||||
searchPlaceholder?: string;
|
||||
searchValue?: string;
|
||||
onSearch?: (value: string, callback?: Function) => void;
|
||||
onClearSearch?: (callback?: Function) => void;
|
||||
items?: TSelectorItem[];
|
||||
onSelect?: (item: TSelectorItem) => void;
|
||||
isMultiSelect?: boolean;
|
||||
selectedItems?: TSelectorItem[];
|
||||
acceptButtonLabel?: string;
|
||||
onAccept: (
|
||||
selectedItems: TSelectorItem[],
|
||||
access: AccessRight | null,
|
||||
fileName: string,
|
||||
isFooterCheckboxChecked: boolean,
|
||||
) => void;
|
||||
withSelectAll?: boolean;
|
||||
selectAllLabel?: string;
|
||||
selectAllIcon?: string;
|
||||
onSelectAll?: () => void;
|
||||
withAccessRights?: boolean;
|
||||
accessRights?: AccessRight[];
|
||||
selectedAccessRight?: AccessRight;
|
||||
onAccessRightsChange?: (access: AccessRight) => void;
|
||||
withCancelButton?: boolean;
|
||||
cancelButtonLabel?: string;
|
||||
onCancel?: () => void;
|
||||
emptyScreenImage?: string;
|
||||
emptyScreenHeader?: string;
|
||||
emptyScreenDescription?: string;
|
||||
searchEmptyScreenImage?: string;
|
||||
searchEmptyScreenHeader?: string;
|
||||
searchEmptyScreenDescription?: string;
|
||||
hasNextPage?: boolean;
|
||||
isNextPageLoading?: boolean;
|
||||
loadNextPage?:
|
||||
| ((
|
||||
startIndex: number,
|
||||
search?: string,
|
||||
...rest: unknown[]
|
||||
) => Promise<void>)
|
||||
| null;
|
||||
totalItems?: number;
|
||||
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
|
||||
isLoading?: boolean;
|
||||
searchLoader?: React.ReactNode;
|
||||
rowLoader?: React.ReactNode;
|
||||
withBreadCrumbs?: boolean;
|
||||
breadCrumbs?: TBreadCrumb[];
|
||||
onSelectBreadCrumb?: (item: TBreadCrumb) => void;
|
||||
breadCrumbsLoader?: React.ReactNode;
|
||||
isBreadCrumbsLoading?: boolean;
|
||||
isSearchLoading?: boolean;
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
footerInputHeader?: string;
|
||||
currentFooterInputValue?: string;
|
||||
footerCheckboxLabel?: string;
|
||||
alwaysShowFooter?: boolean;
|
||||
disableAcceptButton?: boolean;
|
||||
export type TSelectorAccessRights =
|
||||
| {
|
||||
withAccessRights: true;
|
||||
accessRights: TAccessRight[];
|
||||
selectedAccessRight: TAccessRight | null;
|
||||
onAccessRightsChange: (access: TAccessRight) => void;
|
||||
}
|
||||
| {
|
||||
withAccessRights?: undefined;
|
||||
accessRights?: undefined;
|
||||
selectedAccessRight?: undefined;
|
||||
onAccessRightsChange?: undefined;
|
||||
};
|
||||
|
||||
descriptionText?: string;
|
||||
// footer input
|
||||
|
||||
acceptButtonId?: string;
|
||||
cancelButtonId?: string;
|
||||
isChecked?: boolean;
|
||||
setIsChecked?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
export type TSelectorInput =
|
||||
| {
|
||||
withFooterInput: true;
|
||||
footerInputHeader: string;
|
||||
currentFooterInputValue: string;
|
||||
}
|
||||
| {
|
||||
withFooterInput?: undefined;
|
||||
footerInputHeader?: undefined;
|
||||
currentFooterInputValue?: undefined;
|
||||
};
|
||||
|
||||
export interface HeaderProps {
|
||||
onBackClickAction?: () => void;
|
||||
withoutBackButton?: boolean;
|
||||
headerLabel: string;
|
||||
}
|
||||
|
||||
export interface BodyProps {
|
||||
footerVisible: boolean;
|
||||
withHeader?: boolean;
|
||||
isSearch: boolean;
|
||||
isAllIndeterminate?: boolean;
|
||||
isAllChecked?: boolean;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
withSearch?: boolean;
|
||||
onSearch: (value: string) => void;
|
||||
onClearSearch: () => void;
|
||||
items: TSelectorItem[];
|
||||
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
|
||||
onSelect?: (item: TSelectorItem) => void;
|
||||
isMultiSelect?: boolean;
|
||||
withSelectAll?: boolean;
|
||||
selectAllLabel?: string;
|
||||
selectAllIcon?: string;
|
||||
onSelectAll?: () => void;
|
||||
emptyScreenImage?: string;
|
||||
emptyScreenHeader?: string;
|
||||
emptyScreenDescription?: string;
|
||||
searchEmptyScreenImage?: string;
|
||||
searchEmptyScreenHeader?: string;
|
||||
searchEmptyScreenDescription?: string;
|
||||
loadMoreItems: (startIndex: number) => void;
|
||||
hasNextPage?: boolean;
|
||||
isNextPageLoading?: boolean;
|
||||
totalItems: number;
|
||||
isLoading?: boolean;
|
||||
searchLoader: React.ReactNode;
|
||||
rowLoader: React.ReactNode;
|
||||
withBreadCrumbs?: boolean;
|
||||
breadCrumbs?: TBreadCrumb[];
|
||||
onSelectBreadCrumb?: (item: TBreadCrumb) => void;
|
||||
breadCrumbsLoader?: React.ReactNode;
|
||||
isBreadCrumbsLoading?: boolean;
|
||||
isSearchLoading?: boolean;
|
||||
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
|
||||
descriptionText?: string;
|
||||
}
|
||||
|
||||
export interface FooterProps {
|
||||
isMultiSelect?: boolean;
|
||||
acceptButtonLabel: string;
|
||||
selectedItemsCount: number;
|
||||
withCancelButton?: boolean;
|
||||
cancelButtonLabel?: string;
|
||||
withAccessRights?: boolean;
|
||||
accessRights?: AccessRight[];
|
||||
selectedAccessRight?: AccessRight | null;
|
||||
disableAcceptButton?: boolean;
|
||||
onAccept?: () => void;
|
||||
onCancel?: () => void;
|
||||
onChangeAccessRights?: (access: AccessRight) => void;
|
||||
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
footerInputHeader?: string;
|
||||
currentFooterInputValue?: string;
|
||||
footerCheckboxLabel?: string;
|
||||
setNewFooterInputValue?: (value: string) => void;
|
||||
isFooterCheckboxChecked?: boolean;
|
||||
setIsFooterCheckboxChecked?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setIsChecked?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
||||
acceptButtonId?: string;
|
||||
cancelButtonId?: string;
|
||||
}
|
||||
|
||||
export type TSelectorItem = {
|
||||
key?: string;
|
||||
id?: string | number;
|
||||
label: string;
|
||||
avatar?: string;
|
||||
icon?: string;
|
||||
role?: AvatarRole;
|
||||
isSelected?: boolean;
|
||||
email?: string;
|
||||
isDisabled?: boolean;
|
||||
color?: string;
|
||||
fileExst?: string;
|
||||
roomType?: RoomsType;
|
||||
shared: boolean;
|
||||
export type TSelectorFooterInput = TSelectorInput & {
|
||||
setNewFooterInputValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
};
|
||||
|
||||
export interface SearchProps {
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
onSearch: (value: string) => void;
|
||||
onClearSearch: () => void;
|
||||
}
|
||||
// footer checkbox
|
||||
|
||||
export type TSelectorCheckbox =
|
||||
| {
|
||||
withFooterCheckbox: true;
|
||||
footerCheckboxLabel: string;
|
||||
isChecked: boolean;
|
||||
setIsChecked: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
| {
|
||||
withFooterCheckbox?: undefined;
|
||||
footerCheckboxLabel?: undefined;
|
||||
isChecked: boolean;
|
||||
setIsChecked?: undefined;
|
||||
};
|
||||
|
||||
export type TSelectorFooterCheckbox = TSelectorCheckbox & {
|
||||
setIsFooterCheckboxChecked: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export type SelectorProps = TSelectorHeader &
|
||||
TSelectorSelectAll &
|
||||
TSelectorEmptyScreen &
|
||||
TSelectorSearch &
|
||||
TSelectorBreadCrumbs &
|
||||
TSelectorSubmitButton &
|
||||
TSelectorCancelButton &
|
||||
TSelectorAccessRights &
|
||||
TSelectorInput &
|
||||
TSelectorCheckbox & {
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
|
||||
items: TSelectorItem[];
|
||||
onSelect?: (item: TSelectorItem) => void;
|
||||
|
||||
isMultiSelect: boolean;
|
||||
selectedItems?: TSelectorItem[];
|
||||
|
||||
withAccessRights?: boolean;
|
||||
accessRights?: TAccessRight[];
|
||||
selectedAccessRight?: TAccessRight;
|
||||
onAccessRightsChange?: (access: TAccessRight) => void;
|
||||
|
||||
loadNextPage:
|
||||
| ((
|
||||
startIndex: number,
|
||||
search?: string,
|
||||
...rest: unknown[]
|
||||
) => Promise<void>)
|
||||
| null;
|
||||
hasNextPage: boolean;
|
||||
isNextPageLoading: boolean;
|
||||
totalItems: number;
|
||||
isLoading: boolean;
|
||||
|
||||
rowLoader: React.ReactNode;
|
||||
|
||||
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
|
||||
|
||||
alwaysShowFooter?: boolean;
|
||||
descriptionText?: string;
|
||||
};
|
||||
|
||||
export type BodyProps = TSelectorBreadCrumbs &
|
||||
TSelectorBodySearch &
|
||||
TSelectorSelectAll &
|
||||
TSelectorEmptyScreen &
|
||||
TSelectorBreadCrumbs & {
|
||||
footerVisible: boolean;
|
||||
withHeader?: boolean;
|
||||
|
||||
value?: string;
|
||||
|
||||
isMultiSelect: boolean;
|
||||
|
||||
items: TSelectorItem[];
|
||||
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
|
||||
onSelect: (item: TSelectorItem) => void;
|
||||
|
||||
loadMoreItems: (startIndex: number) => void;
|
||||
hasNextPage: boolean;
|
||||
isNextPageLoading: boolean;
|
||||
totalItems: number;
|
||||
isLoading: boolean;
|
||||
|
||||
rowLoader: React.ReactNode;
|
||||
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
descriptionText?: string;
|
||||
};
|
||||
|
||||
export type FooterProps = TSelectorFooterSubmitButton &
|
||||
TSelectorCancelButton &
|
||||
TSelectorAccessRights &
|
||||
TSelectorFooterInput &
|
||||
TSelectorFooterCheckbox & {
|
||||
isMultiSelect: boolean;
|
||||
selectedItemsCount: number;
|
||||
};
|
||||
|
||||
type TSelectorItemLogo =
|
||||
| {
|
||||
color?: undefined;
|
||||
icon?: undefined;
|
||||
avatar: string;
|
||||
role?: AvatarRole;
|
||||
}
|
||||
| { color: string; icon: undefined; avatar?: string; role?: undefined }
|
||||
| { color?: undefined; icon: string; avatar?: undefined; role?: undefined };
|
||||
|
||||
type TSelectorItemType =
|
||||
| {
|
||||
email: string;
|
||||
fileExst?: undefined;
|
||||
roomType?: undefined;
|
||||
shared?: undefined;
|
||||
}
|
||||
| {
|
||||
email?: undefined;
|
||||
fileExst: string;
|
||||
roomType?: undefined;
|
||||
shared?: boolean;
|
||||
}
|
||||
| {
|
||||
email?: undefined;
|
||||
fileExst?: undefined;
|
||||
roomType: RoomsType;
|
||||
shared?: boolean;
|
||||
}
|
||||
| {
|
||||
email?: undefined;
|
||||
fileExst?: undefined;
|
||||
roomType?: undefined;
|
||||
shared?: boolean;
|
||||
};
|
||||
|
||||
export type TSelectorItem = TSelectorItemLogo &
|
||||
TSelectorItemType & {
|
||||
key?: string;
|
||||
id?: string | number;
|
||||
label: string;
|
||||
|
||||
isSelected?: boolean;
|
||||
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
export type Data = {
|
||||
items: TSelectorItem[];
|
||||
@ -197,40 +373,12 @@ export type Data = {
|
||||
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
|
||||
};
|
||||
|
||||
export interface SelectAllProps {
|
||||
label?: string;
|
||||
icon?: string;
|
||||
onSelectAll?: () => void;
|
||||
isChecked?: boolean;
|
||||
isIndeterminate?: boolean;
|
||||
isLoading?: boolean;
|
||||
rowLoader: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface ItemProps {
|
||||
index: number;
|
||||
style: React.CSSProperties;
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface EmptyScreenProps {
|
||||
image?: string;
|
||||
header?: string;
|
||||
description?: string;
|
||||
searchImage?: string;
|
||||
searchHeader?: string;
|
||||
searchDescription?: string;
|
||||
withSearch: boolean;
|
||||
}
|
||||
|
||||
export type TBreadCrumb = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
isRoom?: boolean;
|
||||
minWidth?: string;
|
||||
onClick?: (e: React.MouseEvent, open: boolean, item: TBreadCrumb) => void;
|
||||
};
|
||||
|
||||
export type TDisplayedItem = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
@ -239,9 +387,3 @@ export type TDisplayedItem = {
|
||||
isRoom?: boolean;
|
||||
listItems?: TBreadCrumb[];
|
||||
};
|
||||
|
||||
export interface BreadCrumbsProps {
|
||||
breadCrumbs?: TBreadCrumb[];
|
||||
onSelectBreadCrumb?: (item: TBreadCrumb) => void;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
@ -30,8 +30,9 @@ const Body = ({
|
||||
isSearch,
|
||||
isAllIndeterminate,
|
||||
isAllChecked,
|
||||
placeholder,
|
||||
value,
|
||||
searchPlaceholder,
|
||||
setIsSearch,
|
||||
searchValue,
|
||||
onSearch,
|
||||
onClearSearch,
|
||||
items,
|
||||
@ -165,14 +166,15 @@ const Body = ({
|
||||
)
|
||||
) : null}
|
||||
|
||||
{isSearchLoading || isBreadCrumbsLoading ? (
|
||||
{(withSearch && isSearchLoading) || isBreadCrumbsLoading ? (
|
||||
searchLoader
|
||||
) : withSearch || isSearch || (itemsCount > 0 && withSearch) ? (
|
||||
) : withSearch || (itemsCount > 0 && withSearch) ? (
|
||||
<Search
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
placeholder={searchPlaceholder}
|
||||
value={searchValue}
|
||||
onSearch={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
setIsSearch={setIsSearch}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@ -180,7 +182,7 @@ const Body = ({
|
||||
<Scrollbar style={{ height: listHeight }}>{rowLoader}</Scrollbar>
|
||||
) : itemsCount === 0 ? (
|
||||
<EmptyScreen
|
||||
withSearch={isSearch && !!value}
|
||||
withSearch={isSearch && !!searchValue}
|
||||
image={emptyScreenImage}
|
||||
header={emptyScreenHeader}
|
||||
description={emptyScreenDescription}
|
||||
|
@ -29,7 +29,7 @@ const BreadCrumbs = ({
|
||||
const onClickItem = React.useCallback(
|
||||
(e: React.MouseEvent, open: boolean, item: TBreadCrumb) => {
|
||||
if (isLoading) return;
|
||||
onSelectBreadCrumb?.(item);
|
||||
onSelectBreadCrumb(item);
|
||||
},
|
||||
[isLoading, onSelectBreadCrumb],
|
||||
);
|
||||
@ -191,7 +191,7 @@ const BreadCrumbs = ({
|
||||
onClick={() => {
|
||||
if (index === displayedItems.length - 1 || isLoading) return;
|
||||
|
||||
onSelectBreadCrumb?.({
|
||||
onSelectBreadCrumb({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
isRoom: item.isRoom,
|
||||
|
@ -12,22 +12,22 @@ import {
|
||||
StyledNewNameContainer,
|
||||
StyledNewNameHeader,
|
||||
} from "../Selector.styled";
|
||||
import { AccessRight, FooterProps } from "../Selector.types";
|
||||
import { TAccessRight, FooterProps } from "../Selector.types";
|
||||
|
||||
const Footer = React.memo(
|
||||
({
|
||||
isMultiSelect,
|
||||
acceptButtonLabel,
|
||||
submitButtonLabel,
|
||||
selectedItemsCount,
|
||||
withCancelButton,
|
||||
cancelButtonLabel,
|
||||
withAccessRights,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccept,
|
||||
disableAcceptButton,
|
||||
onSubmit,
|
||||
disableSubmitButton,
|
||||
onCancel,
|
||||
onChangeAccessRights,
|
||||
onAccessRightsChange,
|
||||
|
||||
withFooterInput,
|
||||
withFooterCheckbox,
|
||||
@ -35,16 +35,16 @@ const Footer = React.memo(
|
||||
footerCheckboxLabel,
|
||||
currentFooterInputValue,
|
||||
setNewFooterInputValue,
|
||||
isFooterCheckboxChecked,
|
||||
isChecked,
|
||||
setIsFooterCheckboxChecked,
|
||||
setIsChecked,
|
||||
acceptButtonId,
|
||||
submitButtonId,
|
||||
cancelButtonId,
|
||||
}: FooterProps) => {
|
||||
const label =
|
||||
selectedItemsCount && isMultiSelect
|
||||
? `${acceptButtonLabel} (${selectedItemsCount})`
|
||||
: acceptButtonLabel;
|
||||
? `${submitButtonLabel} (${selectedItemsCount})`
|
||||
: submitButtonLabel;
|
||||
|
||||
const onChangeFileName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
@ -83,7 +83,7 @@ const Footer = React.memo(
|
||||
{withFooterCheckbox && (
|
||||
<Checkbox
|
||||
label={footerCheckboxLabel}
|
||||
isChecked={isFooterCheckboxChecked}
|
||||
isChecked={isChecked}
|
||||
onChange={onChangeCheckbox}
|
||||
/>
|
||||
)}
|
||||
@ -93,7 +93,7 @@ const Footer = React.memo(
|
||||
{withFooterCheckbox && !withFooterInput && (
|
||||
<Checkbox
|
||||
label={footerCheckboxLabel}
|
||||
isChecked={isFooterCheckboxChecked}
|
||||
isChecked={isChecked}
|
||||
onChange={onChangeCheckbox}
|
||||
className="selector_footer-checkbox"
|
||||
/>
|
||||
@ -101,20 +101,24 @@ const Footer = React.memo(
|
||||
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
id={acceptButtonId}
|
||||
id={submitButtonId}
|
||||
className="button accept-button"
|
||||
label={label}
|
||||
primary
|
||||
scale
|
||||
size={ButtonSize.normal}
|
||||
isDisabled={disableAcceptButton}
|
||||
onClick={onAccept}
|
||||
isDisabled={
|
||||
!withFooterInput
|
||||
? disableSubmitButton
|
||||
: disableSubmitButton && !currentFooterInputValue.trim()
|
||||
}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
|
||||
{withAccessRights && (
|
||||
<StyledComboBox
|
||||
onSelect={(opt?: TOption) =>
|
||||
onChangeAccessRights?.({ ...opt } as AccessRight)
|
||||
onAccessRightsChange?.({ ...opt } as TAccessRight)
|
||||
}
|
||||
options={accessRights as TOption[]}
|
||||
size={ComboBoxSize.content}
|
||||
|
@ -9,7 +9,7 @@ import { StyledHeader } from "../Selector.styled";
|
||||
import { HeaderProps } from "../Selector.types";
|
||||
|
||||
const Header = React.memo(
|
||||
({ onBackClickAction, withoutBackButton, headerLabel }: HeaderProps) => {
|
||||
({ onBackClick, withoutBackButton, headerLabel }: HeaderProps) => {
|
||||
return (
|
||||
<StyledHeader>
|
||||
{!withoutBackButton && (
|
||||
@ -17,7 +17,7 @@ const Header = React.memo(
|
||||
className="arrow-button"
|
||||
iconName={ArrowPathReactSvgUrl}
|
||||
size={17}
|
||||
onClick={onBackClickAction}
|
||||
onClick={onBackClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -50,9 +50,6 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
|
||||
const currentRole = role || AvatarRole.user;
|
||||
|
||||
const defaultIcon = !!color;
|
||||
const isLogo = !!icon || defaultIcon;
|
||||
|
||||
const onChangeAction = () => {
|
||||
onSelect?.(item);
|
||||
};
|
||||
@ -77,22 +74,23 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
className="test-22"
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{!isLogo ? (
|
||||
{avatar ? (
|
||||
<Avatar
|
||||
className="user-avatar"
|
||||
source={avatar || ""}
|
||||
source={avatar}
|
||||
role={currentRole}
|
||||
size={AvatarSize.min}
|
||||
/>
|
||||
) : (
|
||||
) : color ? (
|
||||
<RoomIcon color={color} title={label} showDefault />
|
||||
) : icon ? (
|
||||
<RoomIcon
|
||||
color={color}
|
||||
title={label}
|
||||
showDefault={defaultIcon}
|
||||
imgClassName="room-logo"
|
||||
imgSrc={icon}
|
||||
showDefault={false}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
{renderCustomItem ? (
|
||||
renderCustomItem(label, role, email)
|
||||
) : (
|
||||
|
@ -6,14 +6,35 @@ import { InputSize } from "../../text-input";
|
||||
import { SearchProps } from "../Selector.types";
|
||||
|
||||
const Search = React.memo(
|
||||
({ placeholder, value, onSearch, onClearSearch }: SearchProps) => {
|
||||
({
|
||||
placeholder,
|
||||
value,
|
||||
onSearch,
|
||||
onClearSearch,
|
||||
setIsSearch,
|
||||
}: SearchProps) => {
|
||||
const onClearSearchAction = React.useCallback(() => {
|
||||
onClearSearch?.(() => setIsSearch(false));
|
||||
}, [onClearSearch, setIsSearch]);
|
||||
|
||||
const onSearchAction = React.useCallback(
|
||||
(data: string) => {
|
||||
const v = data.trim();
|
||||
|
||||
if (v === "") return onClearSearchAction();
|
||||
|
||||
onSearch?.(v, () => setIsSearch(true));
|
||||
},
|
||||
[onClearSearchAction, onSearch, setIsSearch],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchInput
|
||||
className="search-input"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
onChange={onSearchAction}
|
||||
onClearSearch={onClearSearchAction}
|
||||
size={InputSize.base}
|
||||
/>
|
||||
);
|
||||
|
@ -21,7 +21,7 @@ const SelectAll = React.memo(
|
||||
if (e.target instanceof HTMLElement && e.target.closest(".checkbox"))
|
||||
return;
|
||||
|
||||
onSelectAll?.();
|
||||
onSelectAll();
|
||||
};
|
||||
|
||||
return (
|
||||
@ -32,7 +32,7 @@ const SelectAll = React.memo(
|
||||
<>
|
||||
<Avatar
|
||||
className="select-all_avatar"
|
||||
source={icon || ""}
|
||||
source={icon}
|
||||
role={AvatarRole.user}
|
||||
size={AvatarSize.min}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user