Merge branch 'feature/shared-filter' into feature/shared-article
This commit is contained in:
commit
8b68123fba
@ -13,6 +13,8 @@ import { Backdrop } from "@docspace/shared/components/backdrop";
|
||||
import { Portal } from "@docspace/shared/components/portal";
|
||||
import { toastr } from "@docspace/shared/components/toast";
|
||||
|
||||
import { RowLoader, SearchLoader } from "@docspace/shared/skeletons/selector";
|
||||
|
||||
import EmptyScreenFilterAltSvgUrl from "PUBLIC_DIR/images/empty_screen_filter_alt.svg?url";
|
||||
import EmptyScreenFilterAltDarkSvgUrl from "PUBLIC_DIR/images/empty_screen_filter_alt_dark.svg?url";
|
||||
import EmptyScreenAltSvgUrl from "PUBLIC_DIR/images/empty_screen_alt.svg?url";
|
||||
@ -552,13 +554,13 @@ const FilesSelector = ({
|
||||
isBreadCrumbsLoading={showBreadCrumbsLoader}
|
||||
withSearch={!isRoot && items ? items.length > 0 : !isRoot && isFirstLoad}
|
||||
rowLoader={
|
||||
<Loaders.SelectorRowLoader
|
||||
<RowLoader
|
||||
isMultiSelect={false}
|
||||
isUser={isRoot}
|
||||
isContainer={showLoader}
|
||||
/>
|
||||
}
|
||||
searchLoader={<Loaders.SelectorSearchLoader />}
|
||||
searchLoader={<SearchLoader />}
|
||||
breadCrumbsLoader={<Loaders.SelectorBreadCrumbsLoader />}
|
||||
alwaysShowFooter={true}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
|
@ -1,40 +0,0 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import Backend from "@docspace/shared/utils/i18next-http-backend";
|
||||
import { LANGUAGE } from "@docspace/shared/constants";
|
||||
import config from "PACKAGE_FILE";
|
||||
import { getCookie } from "@docspace/shared/utils";
|
||||
import { loadLanguagePath } from "SRC_DIR/helpers/utils";
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
lng: getCookie(LANGUAGE) || "en",
|
||||
|
||||
fallbackLng: "en",
|
||||
load: "currentOnly",
|
||||
//debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === "lowercase") return value.toLowerCase();
|
||||
return value;
|
||||
},
|
||||
},
|
||||
|
||||
backend: {
|
||||
loadPath: loadLanguagePath(config.homepage),
|
||||
},
|
||||
|
||||
ns: ["PeopleSelector", "Common", "PeopleTranslations"],
|
||||
defaultNS: "PeopleSelector",
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default newInstance;
|
@ -1,286 +0,0 @@
|
||||
import i18n from "./i18n";
|
||||
import PropTypes from "prop-types";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { I18nextProvider, withTranslation } from "react-i18next";
|
||||
|
||||
import { Selector } from "@docspace/shared/components/selector";
|
||||
|
||||
import { getUserRole } from "@docspace/shared/utils/common";
|
||||
import Filter from "@docspace/shared/api/people/filter";
|
||||
import { getUserList } from "@docspace/shared/api/people";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { EmployeeStatus } from "@docspace/shared/enums";
|
||||
import { LOADER_TIMEOUT } from "@docspace/shared/constants";
|
||||
|
||||
import useLoadingWithTimeout from "SRC_DIR/Hooks/useLoadingWithTimeout";
|
||||
|
||||
import DefaultUserPhoto from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
|
||||
|
||||
import EmptyScreenPersonsSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
|
||||
import CatalogAccountsReactSvgUrl from "PUBLIC_DIR/images/catalog.accounts.react.svg?url";
|
||||
import EmptyScreenPersonsSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
|
||||
|
||||
const PeopleSelector = ({
|
||||
acceptButtonLabel,
|
||||
accessRights,
|
||||
cancelButtonLabel,
|
||||
className,
|
||||
emptyScreenDescription,
|
||||
emptyScreenHeader,
|
||||
headerLabel,
|
||||
id,
|
||||
isMultiSelect,
|
||||
items,
|
||||
onAccept,
|
||||
onAccessRightsChange,
|
||||
onBackClick,
|
||||
onCancel,
|
||||
onSelect,
|
||||
onSelectAll,
|
||||
searchEmptyScreenDescription,
|
||||
searchEmptyScreenHeader,
|
||||
searchPlaceholder,
|
||||
selectAllIcon,
|
||||
selectAllLabel,
|
||||
selectedAccessRight,
|
||||
selectedItems,
|
||||
style,
|
||||
t,
|
||||
withAccessRights,
|
||||
withCancelButton,
|
||||
withSelectAll,
|
||||
filter,
|
||||
excludeItems,
|
||||
currentUserId,
|
||||
theme,
|
||||
withOutCurrentAuthorizedUser,
|
||||
withAbilityCreateRoomUsers,
|
||||
withFooterCheckbox,
|
||||
footerCheckboxLabel,
|
||||
isChecked,
|
||||
setIsChecked,
|
||||
filterUserId,
|
||||
}) => {
|
||||
const [itemsList, setItemsList] = useState(items);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [total, setTotal] = useState(0);
|
||||
const [hasNextPage, setHasNextPage] = useState(true);
|
||||
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useLoadingWithTimeout(
|
||||
LOADER_TIMEOUT,
|
||||
false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
loadNextPage(0);
|
||||
}, []);
|
||||
|
||||
const toListItem = (item) => {
|
||||
const {
|
||||
id,
|
||||
email,
|
||||
avatar,
|
||||
icon,
|
||||
displayName,
|
||||
hasAvatar,
|
||||
isOwner,
|
||||
isAdmin,
|
||||
isVisitor,
|
||||
isCollaborator,
|
||||
} = item;
|
||||
|
||||
const role = getUserRole(item);
|
||||
|
||||
const userAvatar = hasAvatar ? avatar : DefaultUserPhoto;
|
||||
|
||||
return {
|
||||
id,
|
||||
email,
|
||||
avatar: userAvatar,
|
||||
icon,
|
||||
label: displayName || email,
|
||||
role,
|
||||
isOwner,
|
||||
isAdmin,
|
||||
isVisitor,
|
||||
isCollaborator,
|
||||
hasAvatar,
|
||||
};
|
||||
};
|
||||
|
||||
const moveCurrentUserToTopOfList = (listUser) => {
|
||||
const currentUserIndex = listUser.findIndex(
|
||||
(user) => user.id === currentUserId
|
||||
);
|
||||
|
||||
// return if the current user is already at the top of the list or not found
|
||||
if (currentUserIndex < 1) return listUser;
|
||||
|
||||
const [currentUser] = listUser.splice(currentUserIndex, 1);
|
||||
|
||||
listUser.splice(0, 0, currentUser);
|
||||
|
||||
return listUser;
|
||||
};
|
||||
|
||||
const removeCurrentUserFromList = (listUser) => {
|
||||
if (filterUserId) {
|
||||
return listUser.filter((user) => user.id !== filterUserId);
|
||||
}
|
||||
return listUser.filter((user) => user.id !== currentUserId);
|
||||
};
|
||||
|
||||
const loadNextPage = (startIndex, search = searchValue) => {
|
||||
const pageCount = 100;
|
||||
|
||||
setIsNextPageLoading(true);
|
||||
|
||||
if (startIndex === 0) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
const currentFilter =
|
||||
typeof filter === "function" ? filter() : filter ?? Filter.getDefault();
|
||||
|
||||
currentFilter.page = startIndex / pageCount;
|
||||
currentFilter.pageCount = pageCount;
|
||||
|
||||
if (!!search.length) {
|
||||
currentFilter.search = search;
|
||||
}
|
||||
|
||||
getUserList(currentFilter)
|
||||
.then((response) => {
|
||||
let newItems = startIndex ? itemsList : [];
|
||||
let totalDifferent = startIndex ? response.total - total : 0;
|
||||
|
||||
const items = response.items
|
||||
.filter((item) => {
|
||||
const excludeUser =
|
||||
withAbilityCreateRoomUsers &&
|
||||
((!item.isAdmin && !item.isOwner && !item.isRoomAdmin) ||
|
||||
item.status === EmployeeStatus.Disabled);
|
||||
|
||||
if (excludeItems.includes(item.id) || excludeUser) {
|
||||
totalDifferent++;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((item) => toListItem(item));
|
||||
|
||||
const tempItems = [...newItems, ...items];
|
||||
|
||||
newItems = withOutCurrentAuthorizedUser
|
||||
? removeCurrentUserFromList(tempItems)
|
||||
: moveCurrentUserToTopOfList(tempItems);
|
||||
|
||||
const newTotal = withOutCurrentAuthorizedUser
|
||||
? response.total - totalDifferent - 1
|
||||
: response.total - totalDifferent;
|
||||
|
||||
setHasNextPage(newItems.length < newTotal);
|
||||
setItemsList(newItems);
|
||||
setTotal(newTotal);
|
||||
|
||||
setIsNextPageLoading(false);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => console.log(error));
|
||||
};
|
||||
|
||||
const onSearch = (value) => {
|
||||
setSearchValue(value);
|
||||
loadNextPage(0, value);
|
||||
};
|
||||
|
||||
const onClearSearch = () => {
|
||||
setSearchValue("");
|
||||
loadNextPage(0, "");
|
||||
};
|
||||
|
||||
const emptyScreenImage = theme.isBase
|
||||
? EmptyScreenPersonsSvgUrl
|
||||
: EmptyScreenPersonsSvgDarkUrl;
|
||||
|
||||
return (
|
||||
<Selector
|
||||
id={id}
|
||||
className={className}
|
||||
style={style}
|
||||
headerLabel={headerLabel || t("ListAccounts")}
|
||||
onBackClick={onBackClick}
|
||||
searchPlaceholder={searchPlaceholder || t("Common:Search")}
|
||||
searchValue={searchValue}
|
||||
onSearch={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
items={itemsList}
|
||||
isMultiSelect={isMultiSelect}
|
||||
selectedItems={selectedItems}
|
||||
acceptButtonLabel={acceptButtonLabel || t("Common:SelectAction")}
|
||||
onAccept={onAccept}
|
||||
withSelectAll={withSelectAll}
|
||||
selectAllLabel={selectAllLabel || t("AllAccounts")}
|
||||
selectAllIcon={selectAllIcon}
|
||||
withAccessRights={withAccessRights}
|
||||
accessRights={accessRights}
|
||||
selectedAccessRight={selectedAccessRight}
|
||||
withCancelButton={withCancelButton}
|
||||
cancelButtonLabel={cancelButtonLabel || t("Common:CancelButton")}
|
||||
onCancel={onCancel}
|
||||
emptyScreenImage={emptyScreenImage}
|
||||
emptyScreenHeader={emptyScreenHeader || t("EmptyHeader")}
|
||||
emptyScreenDescription={emptyScreenDescription || t("EmptyDescription")}
|
||||
searchEmptyScreenImage={emptyScreenImage}
|
||||
searchEmptyScreenHeader={
|
||||
searchEmptyScreenHeader || t("People:NotFoundUsers")
|
||||
}
|
||||
searchEmptyScreenDescription={
|
||||
searchEmptyScreenDescription || t("People:NotFoundUsersDescription")
|
||||
}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
totalItems={total}
|
||||
isLoading={isLoading}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
footerCheckboxLabel={footerCheckboxLabel}
|
||||
isChecked={isChecked}
|
||||
setIsChecked={setIsChecked}
|
||||
searchLoader={<Loaders.SelectorSearchLoader />}
|
||||
isSearchLoading={isLoading}
|
||||
rowLoader={<Loaders.SelectorRowLoader isUser isContainer={isLoading} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
PeopleSelector.propTypes = { excludeItems: PropTypes.array };
|
||||
|
||||
PeopleSelector.defaultProps = {
|
||||
excludeItems: [],
|
||||
selectAllIcon: CatalogAccountsReactSvgUrl,
|
||||
};
|
||||
|
||||
const ExtendedPeopleSelector = inject(({ auth }) => {
|
||||
return {
|
||||
theme: auth.settingsStore.theme,
|
||||
currentUserId: auth.userStore.user.id,
|
||||
};
|
||||
})(
|
||||
observer(
|
||||
withTranslation([
|
||||
"PeopleSelector",
|
||||
"PeopleTranslations",
|
||||
"People",
|
||||
"Common",
|
||||
])(PeopleSelector)
|
||||
)
|
||||
);
|
||||
|
||||
export default (props) => (
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ExtendedPeopleSelector {...props} />
|
||||
</I18nextProvider>
|
||||
);
|
@ -1,62 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Avatar } from "@docspace/shared/components/avatar";
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
import StyledUserTooltip from "./StyledUserTooltip";
|
||||
|
||||
const UserTooltip = ({ avatarUrl, label, email, position, theme }) => (
|
||||
<StyledUserTooltip theme={theme}>
|
||||
<div className="block-avatar">
|
||||
<Avatar
|
||||
theme={theme}
|
||||
className="user-avatar"
|
||||
size="min"
|
||||
role="user"
|
||||
source={avatarUrl}
|
||||
userName=""
|
||||
editing={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="block-info">
|
||||
<Text
|
||||
theme={theme}
|
||||
isBold={true}
|
||||
fontSize="13px"
|
||||
fontWeight={600}
|
||||
truncate={true}
|
||||
title={label}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Text
|
||||
theme={theme}
|
||||
color={theme.peopleSelector.textColor}
|
||||
fontSize="13px"
|
||||
className="email-text"
|
||||
truncate={true}
|
||||
title={email}
|
||||
>
|
||||
{email}
|
||||
</Text>
|
||||
<Text
|
||||
theme={theme}
|
||||
fontSize="13px"
|
||||
fontWeight={600}
|
||||
truncate={true}
|
||||
title={position}
|
||||
>
|
||||
{position}
|
||||
</Text>
|
||||
</div>
|
||||
</StyledUserTooltip>
|
||||
);
|
||||
|
||||
UserTooltip.propTypes = {
|
||||
avatarUrl: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
position: PropTypes.string,
|
||||
};
|
||||
|
||||
export default UserTooltip;
|
@ -4,7 +4,7 @@ import { inject, observer } from "mobx-react";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
import PeopleSelector from "SRC_DIR/components/PeopleSelector";
|
||||
import PeopleSelector from "@docspace/shared/selectors/People";
|
||||
|
||||
import Filter from "@docspace/shared/api/people/filter";
|
||||
|
||||
@ -118,6 +118,7 @@ const ChangePortalOwnerDialog = ({
|
||||
onAccept={onAccept}
|
||||
onCancel={onBackClick}
|
||||
onBackClick={onBackClick}
|
||||
currentUserId={id}
|
||||
/>
|
||||
</ModalDialog.Container>
|
||||
)}
|
||||
|
@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import PeopleSelector from "@docspace/client/src/components/PeopleSelector";
|
||||
import PeopleSelector from "@docspace/shared/selectors/People";
|
||||
import { toastr } from "@docspace/shared/components/toast";
|
||||
import { ModalDialog } from "@docspace/shared/components/modal-dialog";
|
||||
import { Backdrop } from "@docspace/shared/components/backdrop";
|
||||
@ -177,23 +177,24 @@ const DataReassignmentDialog = ({
|
||||
visible={visible}
|
||||
onClose={onClosePeopleSelector}
|
||||
containerVisible={selectorVisible}
|
||||
withFooterBorder={true}
|
||||
withBodyScroll={true}
|
||||
withFooterBorder
|
||||
withBodyScroll
|
||||
>
|
||||
<Backdrop
|
||||
onClick={onClosePeopleSelector}
|
||||
visible={selectorVisible}
|
||||
isAside={true}
|
||||
isAside
|
||||
/>
|
||||
<ModalDialog.Container>
|
||||
<PeopleSelector
|
||||
acceptButtonLabel={t("Common:SelectAction")}
|
||||
excludeItems={[user.id]}
|
||||
currentUserId={user.id}
|
||||
onAccept={onAccept}
|
||||
onCancel={onClosePeopleSelector}
|
||||
onBackClick={onTogglePeopleSelector}
|
||||
withCancelButton={true}
|
||||
withAbilityCreateRoomUsers={true}
|
||||
withCancelButton
|
||||
withAbilityCreateRoomUsers
|
||||
/>
|
||||
</ModalDialog.Container>
|
||||
</StyledModalDialog>
|
||||
@ -206,8 +207,8 @@ const DataReassignmentDialog = ({
|
||||
visible={visible}
|
||||
onClose={onClose}
|
||||
containerVisible={selectorVisible}
|
||||
withFooterBorder={true}
|
||||
withBodyScroll={true}
|
||||
withFooterBorder
|
||||
withBodyScroll
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
{t("DataReassignmentDialog:DataReassignment")}
|
||||
|
@ -12,7 +12,7 @@ import { getUserRole } from "@docspace/shared/utils/common";
|
||||
import Filter from "@docspace/shared/api/people/filter";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { getMembersList } from "@docspace/shared/api/people";
|
||||
import useLoadingWithTimeout from "SRC_DIR/Hooks/useLoadingWithTimeout";
|
||||
import useLoadingWithTimeout from "@docspace/shared/hooks/useLoadingWithTimeout";
|
||||
import { ShareAccessRights } from "@docspace/shared/enums";
|
||||
import { LOADER_TIMEOUT } from "@docspace/shared/constants";
|
||||
|
||||
@ -23,6 +23,7 @@ import DefaultUserPhoto from "PUBLIC_DIR/images/default_user_photo_size_82-82.pn
|
||||
import EmptyScreenPersonsSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
|
||||
import CatalogAccountsReactSvgUrl from "PUBLIC_DIR/images/catalog.accounts.react.svg?url";
|
||||
import EmptyScreenPersonsSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
|
||||
import { RowLoader, SearchLoader } from "@docspace/shared/skeletons/selector";
|
||||
|
||||
const AddUsersPanel = ({
|
||||
isEncrypted,
|
||||
@ -257,10 +258,10 @@ const AddUsersPanel = ({
|
||||
loadNextPage={loadNextPage}
|
||||
totalItems={total}
|
||||
isLoading={isLoading}
|
||||
searchLoader={<Loaders.SelectorSearchLoader />}
|
||||
searchLoader={<SearchLoader />}
|
||||
isSearchLoading={isLoading && !isLoadingSearch}
|
||||
rowLoader={
|
||||
<Loaders.SelectorRowLoader
|
||||
<RowLoader
|
||||
isUser
|
||||
count={15}
|
||||
isContainer={isLoading}
|
||||
|
@ -3,7 +3,7 @@ import { inject, observer } from "mobx-react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { Aside } from "@docspace/shared/components/aside";
|
||||
import { Backdrop } from "@docspace/shared/components/backdrop";
|
||||
import PeopleSelector from "@docspace/client/src/components/PeopleSelector";
|
||||
import PeopleSelector from "@docspace/shared/selectors/People";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import Filter from "@docspace/shared/api/people/filter";
|
||||
import { EmployeeType, DeviceType } from "@docspace/shared/enums";
|
||||
@ -45,6 +45,7 @@ const ChangeRoomOwner = (props) => {
|
||||
currentDeviceType,
|
||||
roomOwnerId,
|
||||
changeRoomOwner,
|
||||
userId,
|
||||
} = props;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@ -120,6 +121,7 @@ const ChangeRoomOwner = (props) => {
|
||||
setIsChecked={setIsChecked}
|
||||
withOutCurrentAuthorizedUser
|
||||
filterUserId={roomOwnerId}
|
||||
currentUserId={userId}
|
||||
/>
|
||||
</Aside>
|
||||
</StyledChangeRoomOwner>
|
||||
@ -145,7 +147,7 @@ export default inject(
|
||||
setChangeRoomOwnerIsVisible,
|
||||
changeRoomOwnerData,
|
||||
} = dialogsStore;
|
||||
const { settingsStore } = auth;
|
||||
const { settingsStore, userStore } = auth;
|
||||
|
||||
const { selection, bufferSelection } = filesStore;
|
||||
|
||||
@ -157,6 +159,8 @@ export default inject(
|
||||
|
||||
const { currentDeviceType } = settingsStore;
|
||||
|
||||
const { id } = user;
|
||||
|
||||
return {
|
||||
visible: changeRoomOwnerIsVisible,
|
||||
setIsVisible: setChangeRoomOwnerIsVisible,
|
||||
@ -165,6 +169,7 @@ export default inject(
|
||||
roomOwnerId: room?.createdBy?.id,
|
||||
currentDeviceType,
|
||||
changeRoomOwner: filesActionsStore.changeRoomOwner,
|
||||
userId: id,
|
||||
};
|
||||
}
|
||||
)(observer(withTranslation(["Files"])(ChangeRoomOwner)));
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import { useLocation, Navigate } from "react-router-dom";
|
||||
import { AuthenticatedAction, ValidationResult } from "./../helpers/constants";
|
||||
import { Loader } from "@docspace/shared/components/loader";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { checkConfirmLink } from "@docspace/shared/api/user"; //TODO: Move AuthStore
|
||||
import { getObjectByLocation } from "@docspace/shared/utils/common";
|
||||
import { combineUrl } from "@docspace/shared/utils/combineUrl";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { I18nextProvider, withTranslation } from "react-i18next";
|
||||
import { setDocumentTitle } from "SRC_DIR/helpers/utils";
|
||||
import i18n from "./i18n";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Loader } from "@docspace/shared/components/loader";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { combineUrl } from "@docspace/shared/utils/combineUrl";
|
||||
import tryRedirectTo from "@docspace/shared/utils/tryRedirectTo";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Loader } from "@docspace/shared/components/loader";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { loginWithConfirmKey } from "@docspace/shared/api/user";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { combineUrl } from "@docspace/shared/utils/combineUrl";
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { Loader } from "@docspace/shared/components/loader";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { combineUrl } from "@docspace/shared/utils/combineUrl";
|
||||
import tryRedirectTo from "@docspace/shared/utils/tryRedirectTo";
|
||||
import SectionWrapper from "SRC_DIR/components/Section";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
|
@ -9,7 +9,7 @@ import { Button } from "@docspace/shared/components/button";
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
|
||||
import { MAX_FILE_COMMENT_LENGTH } from "@docspace/shared/constants";
|
||||
// import infoPanel from "@docspace/common/components/Section/sub-components/info-panel";
|
||||
// import infoPanel from "@docspace/shared/components/section/sub-components/info-panel";
|
||||
|
||||
const CommentEditor = ({
|
||||
t,
|
||||
|
@ -8,7 +8,7 @@ import result from "lodash/result";
|
||||
|
||||
import { isTablet, isMobile } from "@docspace/shared/utils";
|
||||
import { RoomsTypeValues } from "@docspace/shared/utils/common";
|
||||
import FilterInput from "@docspace/common/components/FilterInput";
|
||||
import FilterInput from "@docspace/shared/components/filter";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { withLayoutSize } from "@docspace/shared/HOC/withLayoutSize";
|
||||
import { getUser } from "@docspace/shared/api/people";
|
||||
@ -43,6 +43,7 @@ import ViewRowsReactSvgUrl from "PUBLIC_DIR/images/view-rows.react.svg?url";
|
||||
import ViewTilesReactSvgUrl from "PUBLIC_DIR/images/view-tiles.react.svg?url";
|
||||
|
||||
import { getRoomInfo } from "@docspace/shared/api/rooms";
|
||||
import { FilterLoader } from "@docspace/shared/skeletons/filter";
|
||||
|
||||
const getAccountLoginType = (filterValues) => {
|
||||
const accountLoginType = result(
|
||||
@ -383,7 +384,7 @@ const SectionFilterContent = ({
|
||||
|
||||
newFilter.withSubfolders =
|
||||
withSubfolders === FilterKeys.excludeSubfolders ? null : "true";
|
||||
console.log(data);
|
||||
|
||||
newFilter.searchInContent = withContent === "true" ? "true" : null;
|
||||
|
||||
const path = location.pathname.split("/filter")[0];
|
||||
@ -2024,11 +2025,10 @@ const SectionFilterContent = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (showFilterLoader) return <Loaders.Filter />;
|
||||
if (showFilterLoader) return <FilterLoader />;
|
||||
|
||||
return (
|
||||
<FilterInput
|
||||
t={t}
|
||||
onFilter={onFilter}
|
||||
getFilterData={getFilterData}
|
||||
getSelectedFilterData={getSelectedFilterData}
|
||||
@ -2057,6 +2057,7 @@ const SectionFilterContent = ({
|
||||
setClearSearch={setClearSearch}
|
||||
onSortButtonClick={onSortButtonClick}
|
||||
currentDeviceType={currentDeviceType}
|
||||
userId={userId}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { withTranslation } from "react-i18next";
|
||||
|
||||
import { showLoader, hideLoader } from "@docspace/shared/utils/common";
|
||||
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import SectionWrapper from "SRC_DIR/components/Section";
|
||||
import DragTooltip from "SRC_DIR/components/DragTooltip";
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { Paging } from "@docspace/shared/components/paging";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { FilterLoader } from "@docspace/shared/skeletons/filter";
|
||||
|
||||
const SectionPagingContent = ({
|
||||
fetchPeople,
|
||||
@ -147,7 +148,7 @@ const SectionPagingContent = ({
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Loaders.Filter />
|
||||
<FilterLoader />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,7 @@ import Article from "@docspace/common/components/Article";
|
||||
import { ArticleHeaderContent, ArticleBodyContent } from "./Article";
|
||||
import { SectionHeaderContent, SectionPagingContent } from "./Section";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import withLoading from "SRC_DIR/HOCs/withLoading";
|
||||
|
||||
import SectionWrapper from "SRC_DIR/components/Section";
|
||||
|
@ -5,7 +5,7 @@ import { Text } from "@docspace/shared/components/text";
|
||||
import { Link } from "@docspace/shared/components/link";
|
||||
import { Button } from "@docspace/shared/components/button";
|
||||
import { Loader } from "@docspace/shared/components/loader";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import SectionWrapper from "SRC_DIR/components/Section";
|
||||
import { mobile, tablet } from "@docspace/shared/utils";
|
||||
import { I18nextProvider, Trans, withTranslation } from "react-i18next";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import SectionWrapper from "SRC_DIR/components/Section";
|
||||
import { SectionHeaderContent, SectionBodyContent } from "./Section";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useLocation, Outlet } from "react-router-dom";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import SectionHeaderContent from "../Home/Section/Header";
|
||||
import SectionFilterContent from "../Home/Section/Filter";
|
||||
import FilesPanels from "../../components/FilesPanels";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { Loader } from "@docspace/shared/components/loader";
|
||||
import { ValidationStatus } from "../../helpers/constants";
|
||||
import SectionWrapper from "SRC_DIR/components/Section";
|
||||
|
@ -4,7 +4,7 @@ import { useParams } from "react-router-dom";
|
||||
import { Button } from "@docspace/shared/components/button";
|
||||
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
|
||||
import AppLoader from "@docspace/common/components/AppLoader";
|
||||
import RoomSelector from "../../components/RoomSelector";
|
||||
import RoomSelector from "@docspace/shared/selectors/Room";
|
||||
import FilesSelector from "../../components/FilesSelector";
|
||||
import {
|
||||
frameCallEvent,
|
||||
|
@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getObjectByLocation } from "@docspace/shared/utils/common";
|
||||
import ErrorContainer from "@docspace/common/components/ErrorContainer";
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import { RectangleSkeleton } from "@docspace/shared/skeletons";
|
||||
import { setDocumentTitle } from "SRC_DIR/helpers/utils";
|
||||
import SectionWrapper from "SRC_DIR/components/Section";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
|
@ -308,9 +308,6 @@ module.exports = (env, argv) => {
|
||||
"./src/components/FilesSelector/FilesSelectorWrapper",
|
||||
"./SelectFolderDialog":
|
||||
"./src/components/FilesSelector/FilesSelectorWrapper",
|
||||
"./PeopleSelector": "./src/components/PeopleSelector",
|
||||
"./PeopleSelector/UserTooltip":
|
||||
"./src/components/PeopleSelector/sub-components/UserTooltip.js",
|
||||
"./BrandingPage":
|
||||
"./src/pages/PortalSettings/categories/common/branding.js",
|
||||
"./WhiteLabelPage":
|
||||
|
@ -1,53 +0,0 @@
|
||||
import styled, { css } from "styled-components";
|
||||
import { SearchInput } from "@docspace/shared/components/search-input";
|
||||
|
||||
const StyledFilterInput = styled.div`
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.filter-input_filter-row {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.filter-input_selected-row {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
margin-bottom: 8px;
|
||||
|
||||
.clear-all-link {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: 12px;
|
||||
`
|
||||
: css`
|
||||
margin-left: 12px;
|
||||
`}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SearchInput)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export { StyledFilterInput, StyledSearchInput };
|
@ -1,491 +0,0 @@
|
||||
import React from "react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
import ClearReactSvgUrl from "PUBLIC_DIR/images/clear.react.svg?url";
|
||||
|
||||
import Loaders from "../../Loaders";
|
||||
|
||||
import { Backdrop } from "@docspace/shared/components/backdrop";
|
||||
import { Button } from "@docspace/shared/components/button";
|
||||
import { Heading } from "@docspace/shared/components/heading";
|
||||
import { IconButton } from "@docspace/shared/components/icon-button";
|
||||
import { Scrollbar } from "@docspace/shared/components/scrollbar";
|
||||
import { Portal } from "@docspace/shared/components/portal";
|
||||
|
||||
import FilterBlockItem from "./FilterBlockItem";
|
||||
|
||||
import PeopleSelector from "client/PeopleSelector";
|
||||
import RoomSelector from "@docspace/client/src/components/RoomSelector";
|
||||
|
||||
import {
|
||||
DeviceType,
|
||||
FilterGroups,
|
||||
FilterSelectorTypes,
|
||||
} from "@docspace/shared/enums";
|
||||
|
||||
import {
|
||||
StyledFilterBlock,
|
||||
StyledFilterBlockHeader,
|
||||
StyledFilterBlockFooter,
|
||||
StyledControlContainer,
|
||||
StyledCrossIcon,
|
||||
} from "./StyledFilterBlock";
|
||||
|
||||
//TODO: fix translate
|
||||
const FilterBlock = ({
|
||||
t,
|
||||
selectedFilterValue,
|
||||
filterHeader,
|
||||
getFilterData,
|
||||
hideFilterBlock,
|
||||
onFilter,
|
||||
selectorLabel,
|
||||
isPersonalRoom,
|
||||
isRooms,
|
||||
isAccounts,
|
||||
currentDeviceType,
|
||||
}) => {
|
||||
const [showSelector, setShowSelector] = React.useState({
|
||||
show: false,
|
||||
type: null,
|
||||
group: "",
|
||||
});
|
||||
|
||||
const [filterData, setFilterData] = React.useState([]);
|
||||
const [filterValues, setFilterValues] = React.useState([]);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
|
||||
const setFilterDataFn = (data) => {
|
||||
const filterSubject = data.find(
|
||||
(f) => f.group === FilterGroups.roomFilterSubject
|
||||
);
|
||||
|
||||
if (filterSubject) {
|
||||
const filterOwner = data.find(
|
||||
(f) => f.group === FilterGroups.roomFilterOwner
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
filterSubject.groupItem.findIndex((i) => i.isSelected) > -1;
|
||||
|
||||
filterOwner.groupItem[0].isDisabled = !isSelected;
|
||||
}
|
||||
|
||||
setFilterData(data);
|
||||
};
|
||||
|
||||
const changeShowSelector = React.useCallback((selectorType, group) => {
|
||||
setShowSelector((val) => ({
|
||||
show: !val.show,
|
||||
type: selectorType,
|
||||
group: group,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const changeSelectedItems = React.useCallback(
|
||||
(filter) => {
|
||||
const data = filterData.map((item) => ({ ...item }));
|
||||
|
||||
data.forEach((item) => {
|
||||
if (filter.find((value) => value.group === item.group)) {
|
||||
const currentFilter = filter.find(
|
||||
(value) => value.group === item.group
|
||||
);
|
||||
|
||||
item.groupItem.forEach((groupItem) => {
|
||||
groupItem.isSelected = false;
|
||||
if (groupItem.key === currentFilter.key) {
|
||||
groupItem.isSelected = true;
|
||||
}
|
||||
if (groupItem.displaySelectorType) {
|
||||
groupItem.isSelected = true;
|
||||
groupItem.selectedKey = currentFilter.key;
|
||||
groupItem.selectedLabel = currentFilter.label;
|
||||
}
|
||||
if (groupItem.isMultiSelect) {
|
||||
groupItem.isSelected = currentFilter.key.includes(groupItem.key);
|
||||
}
|
||||
if (groupItem.withOptions) {
|
||||
groupItem.isSelected = currentFilter.key.includes(groupItem.key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
item.groupItem.forEach((groupItem, idx) => {
|
||||
groupItem.isSelected = false;
|
||||
if (groupItem.displaySelectorType) {
|
||||
groupItem.selectedKey = null;
|
||||
groupItem.selectedLabel = null;
|
||||
}
|
||||
if (groupItem.withOptions) {
|
||||
item.groupItem[idx].options.forEach((x, index) => {
|
||||
item.groupItem[idx].options[index].isSelected = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setFilterDataFn(data);
|
||||
},
|
||||
[filterData]
|
||||
);
|
||||
|
||||
const onClearFilter = React.useCallback(() => {
|
||||
changeSelectedItems([]);
|
||||
setFilterValues([]);
|
||||
|
||||
selectedFilterValue &&
|
||||
selectedFilterValue.length > 0 &&
|
||||
onFilter &&
|
||||
onFilter([]);
|
||||
}, [changeSelectedItems, selectedFilterValue?.length]);
|
||||
|
||||
const changeFilterValue = React.useCallback(
|
||||
(group, key, isSelected, label, isMultiSelect, withOptions) => {
|
||||
let value = filterValues.map((value) => {
|
||||
if (typeof value.key === "object") {
|
||||
const newKey = [...value.key];
|
||||
value.key = newKey;
|
||||
}
|
||||
|
||||
return {
|
||||
...value,
|
||||
};
|
||||
});
|
||||
|
||||
if (isSelected) {
|
||||
if (isMultiSelect) {
|
||||
const groupIdx = value.findIndex((item) => item.group === group);
|
||||
|
||||
const itemIdx = value[groupIdx].key.findIndex((item) => item === key);
|
||||
|
||||
value[groupIdx].key.splice(itemIdx, 1);
|
||||
|
||||
if (value[groupIdx].key.length === 0)
|
||||
value = value.filter((item) => item.group !== group);
|
||||
} else {
|
||||
value = value.filter((item) => item.group !== group);
|
||||
}
|
||||
|
||||
setFilterValues(value);
|
||||
changeSelectedItems(value);
|
||||
|
||||
const idx = selectedFilterValue.findIndex(
|
||||
(item) => item.group === group
|
||||
);
|
||||
|
||||
if (idx > -1) {
|
||||
if (isMultiSelect) {
|
||||
const itemIdx = selectedFilterValue[idx].key.findIndex(
|
||||
(item) => item === key
|
||||
);
|
||||
|
||||
if (itemIdx === -1) return;
|
||||
|
||||
selectedFilterValue[idx].key.splice(itemIdx, 1);
|
||||
|
||||
return onFilter(selectedFilterValue);
|
||||
}
|
||||
|
||||
onFilter(value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.find((item) => item.group === group)) {
|
||||
value.forEach((item) => {
|
||||
if (item.group === group) {
|
||||
if (isMultiSelect) {
|
||||
item.key.push(key);
|
||||
} else {
|
||||
item.key = key;
|
||||
if (label) {
|
||||
item.label = label;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (label) {
|
||||
value.push({ group, key, label });
|
||||
} else if (isMultiSelect) {
|
||||
value.push({ group, key: [key] });
|
||||
} else {
|
||||
value.push({ group, key });
|
||||
}
|
||||
}
|
||||
|
||||
setFilterValues(value);
|
||||
changeSelectedItems(value);
|
||||
},
|
||||
[selectedFilterValue, filterValues, changeSelectedItems]
|
||||
);
|
||||
|
||||
const getDefaultFilterData = React.useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
const data = await getFilterData();
|
||||
|
||||
const items = data.filter((item) => item.isHeader === true);
|
||||
|
||||
items.forEach((item) => {
|
||||
const groupItem = data.filter(
|
||||
(val) => val.group === item.group && val.isHeader !== true
|
||||
);
|
||||
|
||||
groupItem.forEach((item) => (item.isSelected = false));
|
||||
|
||||
item.groupItem = groupItem;
|
||||
});
|
||||
|
||||
if (selectedFilterValue) {
|
||||
selectedFilterValue.forEach((selectedValue) => {
|
||||
items.forEach((item) => {
|
||||
if (item.group === selectedValue.group) {
|
||||
item.groupItem.forEach((groupItem) => {
|
||||
if (
|
||||
groupItem.key === selectedValue.key ||
|
||||
groupItem.displaySelectorType
|
||||
) {
|
||||
groupItem.isSelected = true;
|
||||
if (groupItem.displaySelectorType) {
|
||||
groupItem.selectedLabel = selectedValue.label;
|
||||
groupItem.selectedKey = selectedValue.key;
|
||||
}
|
||||
}
|
||||
|
||||
if (groupItem.isMultiSelect) {
|
||||
groupItem.isSelected = selectedValue.key.includes(
|
||||
groupItem.key
|
||||
);
|
||||
}
|
||||
|
||||
if (groupItem.withOptions) {
|
||||
groupItem.options.forEach(
|
||||
(option) =>
|
||||
(option.isSelected = option.key === selectedValue.key)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const newFilterValues = selectedFilterValue.map((value) => {
|
||||
if (typeof value.key === "object") {
|
||||
const newKey = [...value.key];
|
||||
value.key = newKey;
|
||||
}
|
||||
|
||||
return {
|
||||
...value,
|
||||
};
|
||||
});
|
||||
|
||||
setFilterDataFn(items);
|
||||
setFilterValues(newFilterValues);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
getDefaultFilterData();
|
||||
}, []);
|
||||
|
||||
const onFilterAction = React.useCallback(() => {
|
||||
onFilter && onFilter(filterValues);
|
||||
hideFilterBlock();
|
||||
}, [onFilter, hideFilterBlock, filterValues]);
|
||||
|
||||
const onArrowClick = React.useCallback(() => {
|
||||
setShowSelector((val) => ({ ...val, show: false }));
|
||||
}, []);
|
||||
|
||||
const selectOption = React.useCallback(
|
||||
(items) => {
|
||||
setShowSelector((val) => ({
|
||||
...val,
|
||||
show: false,
|
||||
}));
|
||||
|
||||
changeFilterValue(showSelector.group, items[0].id, false, items[0].label);
|
||||
},
|
||||
[showSelector.group, changeFilterValue]
|
||||
);
|
||||
|
||||
const isEqualFilter = () => {
|
||||
let isEqual = true;
|
||||
|
||||
if (
|
||||
filterValues.length === 0 ||
|
||||
selectedFilterValue.length > filterValues.length
|
||||
)
|
||||
return !isEqual;
|
||||
|
||||
if (
|
||||
(selectedFilterValue.length === 0 && filterValues.length > 0) ||
|
||||
selectedFilterValue.length !== filterValues.length
|
||||
) {
|
||||
isEqual = false;
|
||||
|
||||
return !isEqual;
|
||||
}
|
||||
|
||||
filterValues.forEach((value) => {
|
||||
const oldValue = selectedFilterValue.find(
|
||||
(item) => item.group === value.group
|
||||
);
|
||||
|
||||
let isMultiSelectEqual = false;
|
||||
let withOptionsEqual = false;
|
||||
|
||||
if (typeof value.key === "object") {
|
||||
isMultiSelectEqual = true;
|
||||
value.key.forEach(
|
||||
(item) =>
|
||||
(isMultiSelectEqual =
|
||||
isMultiSelectEqual && oldValue.key.includes(item))
|
||||
);
|
||||
}
|
||||
|
||||
if (value.options) {
|
||||
withOptionsEqual = true;
|
||||
value.options.forEach(
|
||||
(option) =>
|
||||
(withOptionsEqual =
|
||||
isMultiSelectEqual && option.key === oldValue.key)
|
||||
);
|
||||
}
|
||||
|
||||
isEqual =
|
||||
isEqual &&
|
||||
(oldValue?.key === value.key || isMultiSelectEqual || withOptionsEqual);
|
||||
});
|
||||
|
||||
return !isEqual;
|
||||
};
|
||||
|
||||
const showFooter = isEqualFilter();
|
||||
|
||||
const filterBlockComponent = (
|
||||
<>
|
||||
{showSelector.show ? (
|
||||
<>
|
||||
<StyledFilterBlock>
|
||||
{showSelector.type === FilterSelectorTypes.people ? (
|
||||
<PeopleSelector
|
||||
withOutCurrentAuthorizedUser
|
||||
className="people-selector"
|
||||
isMultiSelect={false}
|
||||
onAccept={selectOption}
|
||||
onBackClick={onArrowClick}
|
||||
headerLabel={selectorLabel}
|
||||
/>
|
||||
) : (
|
||||
<RoomSelector
|
||||
className="people-selector"
|
||||
isMultiSelect={false}
|
||||
onAccept={selectOption}
|
||||
onBackClick={onArrowClick}
|
||||
headerLabel={selectorLabel}
|
||||
/>
|
||||
)}
|
||||
<StyledControlContainer onClick={hideFilterBlock}>
|
||||
<StyledCrossIcon />
|
||||
</StyledControlContainer>
|
||||
</StyledFilterBlock>
|
||||
</>
|
||||
) : (
|
||||
<StyledFilterBlock showFooter={showFooter}>
|
||||
<StyledFilterBlockHeader>
|
||||
<Heading size="medium">{filterHeader}</Heading>
|
||||
{showFooter && (
|
||||
<IconButton
|
||||
id="filter_search-options-clear"
|
||||
iconName={ClearReactSvgUrl}
|
||||
isFill={true}
|
||||
onClick={onClearFilter}
|
||||
size={17}
|
||||
/>
|
||||
)}
|
||||
</StyledFilterBlockHeader>
|
||||
<div className="filter-body">
|
||||
{isLoading ? (
|
||||
<Loaders.FilterBlock isRooms={isRooms} isAccounts={isAccounts} />
|
||||
) : (
|
||||
<Scrollbar className="filter-body__scrollbar">
|
||||
{filterData.map((item, index) => {
|
||||
return (
|
||||
<FilterBlockItem
|
||||
key={item.key}
|
||||
label={item.label}
|
||||
keyProp={item.key}
|
||||
group={item.group}
|
||||
groupItem={item.groupItem}
|
||||
isLast={item.isLast}
|
||||
isFirst={index === 0}
|
||||
withoutHeader={item.withoutHeader}
|
||||
withoutSeparator={item.withoutSeparator}
|
||||
changeFilterValue={changeFilterValue}
|
||||
showSelector={changeShowSelector}
|
||||
withMultiItems={item.withMultiItems}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Scrollbar>
|
||||
)}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<StyledFilterBlockFooter>
|
||||
<Button
|
||||
id="filter_apply-button"
|
||||
size="normal"
|
||||
primary={true}
|
||||
label={t("Common:ApplyButton")}
|
||||
scale={true}
|
||||
onClick={onFilterAction}
|
||||
/>
|
||||
<Button
|
||||
id="filter_cancel-button"
|
||||
size="normal"
|
||||
label={t("Common:CancelButton")}
|
||||
scale={true}
|
||||
onClick={hideFilterBlock}
|
||||
/>
|
||||
</StyledFilterBlockFooter>
|
||||
)}
|
||||
|
||||
<StyledControlContainer id="filter_close" onClick={hideFilterBlock}>
|
||||
<StyledCrossIcon />
|
||||
</StyledControlContainer>
|
||||
</StyledFilterBlock>
|
||||
)}
|
||||
|
||||
<Backdrop
|
||||
visible={true}
|
||||
withBackground={true}
|
||||
onClick={hideFilterBlock}
|
||||
zIndex={215}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
const renderPortalFilterBlock = () => {
|
||||
const rootElement = document.getElementById("root");
|
||||
|
||||
return (
|
||||
<Portal
|
||||
element={filterBlockComponent}
|
||||
appendTo={rootElement}
|
||||
visible={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return renderPortalFilterBlock();
|
||||
};
|
||||
|
||||
export default React.memo(withTranslation("Common")(FilterBlock));
|
@ -1,357 +0,0 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { withTranslation } from "react-i18next";
|
||||
|
||||
import { ComboBox } from "@docspace/shared/components/combobox";
|
||||
import { DropDownItem } from "@docspace/shared/components/drop-down-item";
|
||||
import { IconButton } from "@docspace/shared/components/icon-button";
|
||||
import { ViewSelector } from "@docspace/shared/components/view-selector";
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
|
||||
import { isMobile, mobile } from "@docspace/shared/utils";
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
|
||||
import SortDesc from "PUBLIC_DIR/images/sort.desc.react.svg";
|
||||
import SortReactSvgUrl from "PUBLIC_DIR/images/sort.react.svg?url";
|
||||
import { Backdrop } from "@docspace/shared/components/backdrop";
|
||||
import { Events } from "@docspace/shared/enums";
|
||||
|
||||
const selectedViewIcon = css`
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.filterInput.sort.selectedViewIcon};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const notSelectedViewIcon = css`
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.filterInput.sort.viewIcon};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSortButton = styled.div`
|
||||
.combo-button {
|
||||
background: ${(props) =>
|
||||
props.theme.filterInput.sort.background} !important;
|
||||
|
||||
.icon-button_svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.sort-combo-box {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: 8px;
|
||||
`
|
||||
: css`
|
||||
margin-left: 8px;
|
||||
`}
|
||||
|
||||
.dropdown-container {
|
||||
top: 102%;
|
||||
bottom: auto;
|
||||
min-width: 200px;
|
||||
margin-top: 3px;
|
||||
|
||||
.view-selector-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
cursor: auto;
|
||||
|
||||
.view-selector {
|
||||
width: 44px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
cursor: auto;
|
||||
|
||||
.view-selector-icon {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.view-selector-icon:nth-child(1) {
|
||||
${(props) =>
|
||||
props.viewAs === "row" ? selectedViewIcon : notSelectedViewIcon};
|
||||
}
|
||||
|
||||
.view-selector-icon:nth-child(2) {
|
||||
${(props) =>
|
||||
props.viewAs !== "row" ? selectedViewIcon : notSelectedViewIcon};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-width: 200px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.option-item__icon {
|
||||
display: flex;
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
${(props) =>
|
||||
props.isDesc &&
|
||||
css`
|
||||
transform: rotate(180deg);
|
||||
`}
|
||||
|
||||
path {
|
||||
fill: ${(props) => props.theme.filterInput.sort.sortFill};
|
||||
}
|
||||
}
|
||||
|
||||
:hover {
|
||||
.option-item__icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-option-item {
|
||||
background: ${(props) => props.theme.filterInput.sort.hoverBackground};
|
||||
cursor: auto;
|
||||
|
||||
.selected-option-item__icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.optionalBlock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: 0;
|
||||
`
|
||||
: css`
|
||||
margin-right: 0;
|
||||
`}
|
||||
}
|
||||
|
||||
.combo-buttons_arrow-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.backdrop-active {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSortButton.defaultProps = { theme: Base };
|
||||
|
||||
const SortButton = ({
|
||||
id,
|
||||
getSortData,
|
||||
getSelectedSortData,
|
||||
|
||||
onChangeViewAs,
|
||||
view,
|
||||
viewAs,
|
||||
viewSettings,
|
||||
|
||||
onSort,
|
||||
viewSelectorVisible,
|
||||
|
||||
onSortButtonClick,
|
||||
title,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
const [sortData, setSortData] = React.useState([]);
|
||||
const [selectedSortData, setSelectedSortData] = React.useState({
|
||||
sortDirection: null,
|
||||
sortId: null,
|
||||
});
|
||||
|
||||
const getSortDataAction = React.useCallback(() => {
|
||||
const value = getSortData && getSortData();
|
||||
const selectedValue = getSelectedSortData && getSelectedSortData();
|
||||
|
||||
const data = value.map((item) => {
|
||||
item.className = "option-item";
|
||||
item.isSelected = false;
|
||||
if (selectedValue.sortId === item.key) {
|
||||
item.className = item.className + " selected-option-item";
|
||||
item.isSelected = true;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
setSortData(data);
|
||||
|
||||
setSelectedSortData({
|
||||
sortDirection: selectedValue.sortDirection,
|
||||
sortId: selectedValue.sortId,
|
||||
});
|
||||
}, [getSortData, getSelectedSortData, viewAs]);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener(Events.CHANGE_COLUMN, getSortDataAction);
|
||||
getSortDataAction();
|
||||
|
||||
return () =>
|
||||
window.removeEventListener(Events.CHANGE_COLUMN, getSortDataAction);
|
||||
}, [getSortDataAction]);
|
||||
|
||||
const toggleCombobox = React.useCallback(() => {
|
||||
setIsOpen((val) => !val);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
onSortButtonClick && onSortButtonClick(!isOpen);
|
||||
}, [isOpen]);
|
||||
|
||||
const onOptionClick = React.useCallback(
|
||||
(e) => {
|
||||
const key = e.target.closest(".option-item").dataset.value;
|
||||
|
||||
let sortDirection = selectedSortData.sortDirection;
|
||||
|
||||
if (key === selectedSortData.sortId) {
|
||||
sortDirection = sortDirection === "desc" ? "asc" : "desc";
|
||||
}
|
||||
|
||||
let data = sortData.map((item) => ({ ...item }));
|
||||
|
||||
data = data.map((item) => {
|
||||
item.className = "option-item";
|
||||
item.isSelected = false;
|
||||
if (key === item.key) {
|
||||
item.className = item.className + " selected-option-item";
|
||||
item.isSelected = true;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
setSortData(data);
|
||||
|
||||
setSelectedSortData({
|
||||
sortId: key,
|
||||
sortDirection: sortDirection,
|
||||
});
|
||||
|
||||
// toggleCombobox();
|
||||
|
||||
onSort && onSort(key, sortDirection);
|
||||
},
|
||||
[onSort, toggleCombobox, sortData, selectedSortData]
|
||||
);
|
||||
|
||||
const advancedOptions = (
|
||||
<>
|
||||
{viewSelectorVisible && (
|
||||
<>
|
||||
<DropDownItem noHover className="view-selector-item">
|
||||
<Text fontWeight={600}>{view}</Text>
|
||||
<ViewSelector
|
||||
className="view-selector"
|
||||
onChangeView={onChangeViewAs}
|
||||
viewAs={viewAs}
|
||||
viewSettings={viewSettings}
|
||||
/>
|
||||
</DropDownItem>
|
||||
|
||||
<DropDownItem isSeparator={true}></DropDownItem>
|
||||
</>
|
||||
)}
|
||||
{sortData?.map((item) => (
|
||||
<DropDownItem
|
||||
id={item.id}
|
||||
onClick={onOptionClick}
|
||||
className={item.className}
|
||||
key={item.key}
|
||||
data-value={item.key}
|
||||
>
|
||||
<Text fontWeight={600}>{item.label}</Text>
|
||||
<SortDesc
|
||||
className={`option-item__icon${
|
||||
item.isSelected ? " selected-option-item__icon" : ""
|
||||
}`}
|
||||
/>
|
||||
</DropDownItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
let advancedOptionsCount = sortData.length;
|
||||
|
||||
if (viewSelectorVisible) {
|
||||
advancedOptionsCount++;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Backdrop
|
||||
visible={isOpen}
|
||||
withBackground={isMobile()}
|
||||
onClick={toggleCombobox}
|
||||
withoutBlur={!isMobile()}
|
||||
/>
|
||||
<StyledSortButton
|
||||
viewAs={viewAs}
|
||||
isDesc={selectedSortData.sortDirection === "desc"}
|
||||
onClick={toggleCombobox}
|
||||
id={id}
|
||||
title={title}
|
||||
>
|
||||
<ComboBox
|
||||
opened={isOpen}
|
||||
onToggle={toggleCombobox}
|
||||
className={"sort-combo-box"}
|
||||
options={[]}
|
||||
selectedOption={{}}
|
||||
directionX={"right"}
|
||||
directionY={"both"}
|
||||
scaled
|
||||
size={"content"}
|
||||
advancedOptions={advancedOptions}
|
||||
disableIconClick={false}
|
||||
disableItemClick
|
||||
isDefaultMode={false}
|
||||
manualY={"102%"}
|
||||
advancedOptionsCount={advancedOptionsCount}
|
||||
displayArrow={false}
|
||||
>
|
||||
<IconButton iconName={SortReactSvgUrl} size={16} />
|
||||
</ComboBox>
|
||||
</StyledSortButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SortButton);
|
@ -1,71 +0,0 @@
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
const StyledButton = styled.div`
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
|
||||
position: relative;
|
||||
|
||||
border: ${(props) => props.theme.filterInput.button.border};
|
||||
border-radius: 3px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: 8px;
|
||||
`
|
||||
: css`
|
||||
margin-left: 8px;
|
||||
`}
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border: ${(props) => props.theme.filterInput.button.hoverBorder};
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.iconButton.hoverColor};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.isOpen &&
|
||||
css`
|
||||
background: ${(props) => props.theme.filterInput.button.openBackground};
|
||||
pointer-events: none;
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.filterInput.button.openFill};
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
margin-top: 5px;
|
||||
min-width: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
`}
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
`;
|
||||
|
||||
StyledButton.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledButton;
|
@ -1,353 +0,0 @@
|
||||
import React from "react";
|
||||
import { RoomsType } from "@docspace/shared/enums";
|
||||
import { RoomsTypeValues } from "@docspace/shared/utils/common";
|
||||
import { RectangleSkeleton } from "@docspace/shared/skeletons";
|
||||
|
||||
import { StyledBlock, StyledContainer } from "./StyledFilterBlockLoader";
|
||||
|
||||
const FilterBlockLoader = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
isRooms,
|
||||
isAccounts,
|
||||
|
||||
...rest
|
||||
}) => {
|
||||
const roomTypeLoader = isRooms ? (
|
||||
<>
|
||||
{Object.values(RoomsTypeValues).map((roomType) => {
|
||||
switch (roomType) {
|
||||
case RoomsType.FillingFormsRoom:
|
||||
return (
|
||||
<RectangleSkeleton
|
||||
key={roomType}
|
||||
width={"77"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
);
|
||||
case RoomsType.EditingRoom:
|
||||
return (
|
||||
<RectangleSkeleton
|
||||
key={roomType}
|
||||
width={"98"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
);
|
||||
case RoomsType.ReviewRoom:
|
||||
return (
|
||||
<RectangleSkeleton
|
||||
key={roomType}
|
||||
width={"112"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
);
|
||||
case RoomsType.ReadOnlyRoom:
|
||||
return (
|
||||
<RectangleSkeleton
|
||||
key={roomType}
|
||||
width={"73"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
);
|
||||
case RoomsType.CustomRoom:
|
||||
default:
|
||||
return (
|
||||
<RectangleSkeleton
|
||||
key={roomType}
|
||||
width={"89"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<StyledContainer id={id} className={className} style={style} {...rest}>
|
||||
{!isRooms && !isAccounts && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width={"50"}
|
||||
height={"16"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"100%"}
|
||||
height={"32"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width={"16"}
|
||||
height={"16"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"137"}
|
||||
height={"20"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
</div>
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
{!isAccounts && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width={"51"}
|
||||
height={"16"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width={"51"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"68"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
</div>
|
||||
{isRooms && (
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width={"16"}
|
||||
height={"16"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"137"}
|
||||
height={"20"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
{(isRooms || isAccounts) && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width={"50"}
|
||||
height={"16"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<div className="row-loader">
|
||||
{isAccounts ? (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width={"67"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"80"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"83"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
</>
|
||||
) : isRooms ? (
|
||||
<>{roomTypeLoader}</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
{isAccounts && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width={"50"}
|
||||
height={"16"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width={"114"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"110"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"108"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"59"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
</div>
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
<StyledBlock isLast>
|
||||
<RectangleSkeleton
|
||||
width={"50"}
|
||||
height={"16"}
|
||||
borderRadius={"3"}
|
||||
className={"loader-item"}
|
||||
/>
|
||||
<div className="row-loader">
|
||||
{isAccounts ? (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width={"57"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"57"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
</>
|
||||
) : isRooms ? (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width={"67"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"73"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"67"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"74"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"65"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"72"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width={"73"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"99"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"114"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"112"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"130"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"66"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"81"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"74"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width={"68"}
|
||||
height={"28"}
|
||||
borderRadius={"16"}
|
||||
className={"loader-item tag-item"}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</StyledBlock>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterBlockLoader;
|
@ -1,41 +0,0 @@
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import {
|
||||
withKnobs,
|
||||
boolean,
|
||||
text,
|
||||
color,
|
||||
number,
|
||||
} from "@storybook/addon-knobs/react";
|
||||
import Section from "../../../../.storybook/decorators/section";
|
||||
|
||||
import Loaders from "..";
|
||||
import { LOADER_STYLE } from "@docspace/shared/constants";
|
||||
import withReadme from "storybook-readme/with-readme";
|
||||
import Readme from "./README.md";
|
||||
|
||||
storiesOf("Components|Loaders", module)
|
||||
.addDecorator(withKnobs)
|
||||
.addDecorator(withReadme(Readme))
|
||||
.add("filter loader", () => (
|
||||
<Section>
|
||||
<h1>Filter Loader</h1>
|
||||
<Loaders.Filter
|
||||
title={text("title", LOADER_STYLE.title)}
|
||||
height={text("height", "32px")}
|
||||
borderRadius={text("borderRadius", "3")}
|
||||
backgroundColor={color("backgroundColor", LOADER_STYLE.backgroundColor)}
|
||||
foregroundColor={color("foregroundColor", LOADER_STYLE.foregroundColor)}
|
||||
backgroundOpacity={number(
|
||||
"backgroundOpacity",
|
||||
LOADER_STYLE.backgroundOpacity
|
||||
)}
|
||||
foregroundOpacity={number(
|
||||
"foregroundOpacity",
|
||||
LOADER_STYLE.foregroundOpacity
|
||||
)}
|
||||
speed={number("speed", LOADER_STYLE.speed)}
|
||||
animate={boolean("animate", LOADER_STYLE.animate)}
|
||||
/>
|
||||
</Section>
|
||||
));
|
@ -1,27 +0,0 @@
|
||||
# Filter Loader
|
||||
|
||||
Component that displays filter loader
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
```
|
||||
|
||||
```jsx
|
||||
<Loaders.Filter />
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| ------------------- | :------: | :------: | :----: | :-------: | ------------------------------------------------ |
|
||||
| `title` | `string` | - | - | `` | It's used to describe what element it is. |
|
||||
| `height` | `string` | - | - | `32` | Sets the height |
|
||||
| `borderRadius` | `string` | - | - | `3` | Sets the corners rounding |
|
||||
| `backgroundColor` | `string` | - | - | `#000000` | Used as background of animation |
|
||||
| `foregroundColor` | `string` | - | - | `#000000` | Used as the foreground of animation |
|
||||
| `backgroundOpacity` | `number` | - | - | 0.2 | Background opacity (0 = transparent, 1 = opaque) |
|
||||
| `foregroundOpacity` | `number` | - | - | 0.15 | Animation opacity (0 = transparent, 1 = opaque) |
|
||||
| `speed` | `number` | - | - | 2 | Animation speed in seconds |
|
||||
| `animate` | `bool` | - | - | true | Opt-out of animations |
|
@ -1 +0,0 @@
|
||||
export default from "./FilterLoader";
|
@ -1,21 +0,0 @@
|
||||
import React from "react";
|
||||
import { RectangleSkeleton } from "@docspace/shared/skeletons";
|
||||
|
||||
const SelectorSearchLoader = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<RectangleSkeleton
|
||||
width={"calc(100% - 16px)"}
|
||||
height={"32px"}
|
||||
style={{ padding: "0 0 0 16px", marginBottom: "8px", ...style }}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectorSearchLoader;
|
@ -10,8 +10,7 @@ import NewTreeFolders from "./NewTreeFolderLoader";
|
||||
import TreeSettingsLoader from "./TreeSettingsLoader";
|
||||
|
||||
import Text from "./TextLoader";
|
||||
import Filter from "./FilterLoader";
|
||||
import FilterBlock from "./FilterBlockLoader";
|
||||
|
||||
import ProfileView from "./ProfileViewLoader";
|
||||
import ProfileFooter from "./ProfileFooterLoader";
|
||||
import Notifications from "./NotificationsLoader";
|
||||
@ -37,8 +36,6 @@ import PaymentsLoader from "./PaymentsLoader";
|
||||
|
||||
import SelectorBreadCrumbsLoader from "./SelectorBreadCrumbsLoader";
|
||||
import PaymentsStandaloneLoader from "./PaymentsStandaloneLoader";
|
||||
import SelectorSearchLoader from "./SelectorSearchLoader";
|
||||
import SelectorRowLoader from "./SelectorRowLoader";
|
||||
|
||||
import InfoPanelViewLoader from "./InfoPanelBodyLoader/InfoPanelViewLoader";
|
||||
import InfoPanelHeaderLoader from "./InfoPanelHeaderLoader";
|
||||
@ -59,8 +56,7 @@ export default {
|
||||
TreeSettingsLoader,
|
||||
|
||||
Text,
|
||||
Filter,
|
||||
FilterBlock,
|
||||
|
||||
ProfileView,
|
||||
ProfileFooter,
|
||||
|
||||
@ -89,8 +85,6 @@ export default {
|
||||
|
||||
SelectorBreadCrumbsLoader,
|
||||
PaymentsStandaloneLoader,
|
||||
SelectorSearchLoader,
|
||||
SelectorRowLoader,
|
||||
|
||||
InfoPanelHeaderLoader,
|
||||
InfoPanelViewLoader,
|
||||
|
@ -1,32 +0,0 @@
|
||||
# Section
|
||||
|
||||
Default section
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import Section from "@docspace/common/components/Section";
|
||||
```
|
||||
|
||||
```jsx
|
||||
<Section withBodyScroll={true}>
|
||||
<Section.SectionHeader>{sectionHeaderContent}</Section.SectionHeader>
|
||||
|
||||
<Section.SectionFilter>{sectionFilterContent}</Section.SectionFilter>
|
||||
|
||||
<Section.SectionBody>{sectionBodyContent}</Section.SectionBody>
|
||||
|
||||
<Section.SectionPaging>{sectionPagingContent}</Section.SectionPaging>
|
||||
</Section>
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| ---------------------- | :----: | :------: | :----: | :-----: | ----------------------------------------- |
|
||||
| `sectionHeaderContent` | `bool` | - | - | - | Section header content |
|
||||
| `sectionFilterContent` | `bool` | - | - | - | Section filter content |
|
||||
| `sectionBodyContent` | `bool` | - | - | - | Section body content |
|
||||
| `sectionPagingContent` | `bool` | - | - | - | Section paging content |
|
||||
| `withBodyScroll` | `bool` | - | - | `true` | If you need display scroll inside content |
|
||||
| `withBodyAutoFocus` | `bool` | - | - | `false` | If you need set focus on content element |
|
@ -1,34 +0,0 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import Backend from "@docspace/shared/utils/i18next-http-backend";
|
||||
import { LANGUAGE } from "@docspace/shared/constants";
|
||||
import { loadLanguagePath } from "@docspace/shared/utils/common";
|
||||
import { getCookie } from "@docspace/shared/utils";
|
||||
|
||||
i18n
|
||||
.use(Backend)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
lng: getCookie(LANGUAGE) || "en",
|
||||
fallbackLng: "en",
|
||||
load: "currentOnly",
|
||||
//debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === "lowercase") return value.toLowerCase();
|
||||
return value;
|
||||
},
|
||||
},
|
||||
|
||||
backend: {
|
||||
loadPath: loadLanguagePath(""),
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
@ -1,423 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes, { element } from "prop-types";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
import { Provider } from "@docspace/shared/utils";
|
||||
|
||||
import SectionContainer from "./sub-components/section-container";
|
||||
import SubSectionHeader from "./sub-components/section-header";
|
||||
import SubSectionFilter from "./sub-components/section-filter";
|
||||
import SubSectionBody from "./sub-components/section-body";
|
||||
import SubSectionBodyContent from "./sub-components/section-body-content";
|
||||
import SubSectionPaging from "./sub-components/section-paging";
|
||||
import InfoPanel from "./sub-components/info-panel";
|
||||
import SubInfoPanelBody from "./sub-components/info-panel-body";
|
||||
import SubInfoPanelHeader from "./sub-components/info-panel-header";
|
||||
import SubSectionFooter from "./sub-components/section-footer";
|
||||
import SectionWarning from "./sub-components/section-warning";
|
||||
|
||||
import { FloatingButton } from "@docspace/shared/components/floating-button";
|
||||
import { DeviceType } from "@docspace/shared/enums";
|
||||
|
||||
const Section = (props) => {
|
||||
const {
|
||||
onDrop,
|
||||
showPrimaryProgressBar,
|
||||
primaryProgressBarIcon,
|
||||
primaryProgressBarValue,
|
||||
showPrimaryButtonAlert,
|
||||
showSecondaryProgressBar,
|
||||
secondaryProgressBarValue,
|
||||
secondaryProgressBarIcon,
|
||||
showSecondaryButtonAlert,
|
||||
uploadFiles,
|
||||
viewAs,
|
||||
withBodyScroll,
|
||||
children,
|
||||
isHeaderVisible,
|
||||
onOpenUploadPanel,
|
||||
isTabletView,
|
||||
maintenanceExist,
|
||||
snackbarExist,
|
||||
showText,
|
||||
isInfoPanelAvailable,
|
||||
settingsStudio,
|
||||
clearUploadedFilesHistory,
|
||||
|
||||
isInfoPanelScrollLocked,
|
||||
isEmptyPage,
|
||||
isTrashFolder,
|
||||
isFormGallery,
|
||||
currentDeviceType,
|
||||
} = props;
|
||||
|
||||
const [sectionSize, setSectionSize] = React.useState({
|
||||
width: null,
|
||||
height: null,
|
||||
});
|
||||
|
||||
const containerRef = React.useRef(null);
|
||||
const timerRef = React.useRef(null);
|
||||
|
||||
let sectionHeaderContent = null;
|
||||
let sectionFilterContent = null;
|
||||
let sectionPagingContent = null;
|
||||
let sectionBodyContent = null;
|
||||
let sectionFooterContent = null;
|
||||
let infoPanelBodyContent = null;
|
||||
let infoPanelHeaderContent = null;
|
||||
let sectionWarningContent = null;
|
||||
|
||||
React.Children.forEach(children, (child) => {
|
||||
const childType =
|
||||
child && child.type && (child.type.displayName || child.type.name);
|
||||
|
||||
switch (childType) {
|
||||
case Section.SectionHeader.displayName:
|
||||
sectionHeaderContent = child;
|
||||
break;
|
||||
case Section.SectionFilter.displayName:
|
||||
sectionFilterContent = child;
|
||||
break;
|
||||
case Section.SectionPaging.displayName:
|
||||
sectionPagingContent = child;
|
||||
break;
|
||||
case Section.SectionBody.displayName:
|
||||
sectionBodyContent = child;
|
||||
break;
|
||||
case Section.SectionFooter.displayName:
|
||||
sectionFooterContent = child;
|
||||
break;
|
||||
case Section.InfoPanelBody.displayName:
|
||||
infoPanelBodyContent = child;
|
||||
break;
|
||||
case Section.InfoPanelHeader.displayName:
|
||||
infoPanelHeaderContent = child;
|
||||
break;
|
||||
case Section.SectionWarning.displayName:
|
||||
sectionWarningContent = child;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const isSectionHeaderAvailable = !!sectionHeaderContent,
|
||||
isSectionFilterAvailable = !!sectionFilterContent,
|
||||
isSectionPagingAvailable = !!sectionPagingContent,
|
||||
isSectionBodyAvailable =
|
||||
!!sectionBodyContent ||
|
||||
isSectionFilterAvailable ||
|
||||
isSectionPagingAvailable,
|
||||
isSectionAvailable =
|
||||
isSectionHeaderAvailable ||
|
||||
isSectionFilterAvailable ||
|
||||
isSectionBodyAvailable ||
|
||||
isSectionPagingAvailable;
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
const ro = new ResizeObserver(onResize);
|
||||
|
||||
!!containerRef.current && ro.observe(containerRef.current);
|
||||
return () => {
|
||||
!!containerRef.current && ro.unobserve(containerRef.current);
|
||||
window.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onResize = React.useCallback(() => {
|
||||
clearTimeout(timerRef.current);
|
||||
|
||||
timerRef.current = setTimeout(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const computedStyles = window.getComputedStyle(
|
||||
containerRef.current,
|
||||
null
|
||||
);
|
||||
const width = +computedStyles.getPropertyValue("width").replace("px", "");
|
||||
const height = +computedStyles
|
||||
.getPropertyValue("height")
|
||||
.replace("px", "");
|
||||
|
||||
setSectionSize(() => ({ width, height }));
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
const showTwoProgress = showPrimaryProgressBar && showSecondaryProgressBar;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSectionAvailable && (
|
||||
<Provider
|
||||
value={{
|
||||
sectionWidth: sectionSize.width,
|
||||
sectionHeight: sectionSize.height,
|
||||
}}
|
||||
>
|
||||
<SectionContainer
|
||||
showText={showText}
|
||||
viewAs={viewAs}
|
||||
ref={containerRef}
|
||||
maintenanceExist={maintenanceExist}
|
||||
isSectionHeaderAvailable={isSectionHeaderAvailable}
|
||||
settingsStudio={settingsStudio}
|
||||
showTwoProgress={showTwoProgress}
|
||||
>
|
||||
{isSectionHeaderAvailable &&
|
||||
currentDeviceType === DeviceType.desktop && (
|
||||
<SubSectionHeader
|
||||
maintenanceExist={maintenanceExist}
|
||||
snackbarExist={snackbarExist}
|
||||
className="section-header_header"
|
||||
isHeaderVisible={isHeaderVisible}
|
||||
viewAs={viewAs}
|
||||
showText={showText}
|
||||
isEmptyPage={isEmptyPage}
|
||||
isTrashFolder={isTrashFolder}
|
||||
isFormGallery={isFormGallery}
|
||||
>
|
||||
{sectionHeaderContent
|
||||
? sectionHeaderContent.props.children
|
||||
: null}
|
||||
</SubSectionHeader>
|
||||
)}
|
||||
{isSectionFilterAvailable &&
|
||||
currentDeviceType === DeviceType.desktop && (
|
||||
<>
|
||||
<SubSectionFilter
|
||||
className="section-header_filter"
|
||||
viewAs={viewAs}
|
||||
>
|
||||
{sectionFilterContent
|
||||
? sectionFilterContent.props.children
|
||||
: null}
|
||||
</SubSectionFilter>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isSectionBodyAvailable && (
|
||||
<>
|
||||
<SubSectionBody
|
||||
onDrop={onDrop}
|
||||
uploadFiles={uploadFiles}
|
||||
withScroll={withBodyScroll}
|
||||
autoFocus={
|
||||
currentDeviceType !== DeviceType.desktop ? false : true
|
||||
}
|
||||
viewAs={viewAs}
|
||||
settingsStudio={settingsStudio}
|
||||
isFormGallery={isFormGallery}
|
||||
>
|
||||
{isSectionHeaderAvailable &&
|
||||
currentDeviceType !== DeviceType.desktop && (
|
||||
<SubSectionHeader
|
||||
className="section-body_header"
|
||||
isHeaderVisible={isHeaderVisible}
|
||||
viewAs={viewAs}
|
||||
showText={showText}
|
||||
settingsStudio={settingsStudio}
|
||||
isEmptyPage={isEmptyPage}
|
||||
isTrashFolder={isTrashFolder}
|
||||
isFormGallery={isFormGallery}
|
||||
>
|
||||
{sectionHeaderContent
|
||||
? sectionHeaderContent.props.children
|
||||
: null}
|
||||
</SubSectionHeader>
|
||||
)}
|
||||
{currentDeviceType !== DeviceType.desktop && (
|
||||
<SectionWarning>
|
||||
{sectionWarningContent
|
||||
? sectionWarningContent.props.children
|
||||
: null}
|
||||
</SectionWarning>
|
||||
)}
|
||||
{isSectionFilterAvailable &&
|
||||
currentDeviceType !== DeviceType.desktop && (
|
||||
<SubSectionFilter className="section-body_filter">
|
||||
{sectionFilterContent
|
||||
? sectionFilterContent.props.children
|
||||
: null}
|
||||
</SubSectionFilter>
|
||||
)}
|
||||
<SubSectionBodyContent>
|
||||
{sectionBodyContent
|
||||
? sectionBodyContent.props.children
|
||||
: null}
|
||||
</SubSectionBodyContent>
|
||||
<SubSectionFooter>
|
||||
{sectionFooterContent
|
||||
? sectionFooterContent.props.children
|
||||
: null}
|
||||
</SubSectionFooter>
|
||||
{isSectionPagingAvailable && (
|
||||
<SubSectionPaging>
|
||||
{sectionPagingContent
|
||||
? sectionPagingContent.props.children
|
||||
: null}
|
||||
</SubSectionPaging>
|
||||
)}
|
||||
</SubSectionBody>
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentDeviceType === DeviceType.desktop ? (
|
||||
showTwoProgress ? (
|
||||
<div className="progress-bar_container">
|
||||
<FloatingButton
|
||||
className="layout-progress-bar"
|
||||
icon={primaryProgressBarIcon}
|
||||
percent={primaryProgressBarValue}
|
||||
alert={showPrimaryButtonAlert}
|
||||
onClick={onOpenUploadPanel}
|
||||
clearUploadedFilesHistory={clearUploadedFilesHistory}
|
||||
/>
|
||||
<FloatingButton
|
||||
className="layout-progress-second-bar"
|
||||
icon={secondaryProgressBarIcon}
|
||||
percent={secondaryProgressBarValue}
|
||||
alert={showSecondaryButtonAlert}
|
||||
showTwoProgress={showTwoProgress}
|
||||
/>
|
||||
</div>
|
||||
) : showPrimaryProgressBar && !showSecondaryProgressBar ? (
|
||||
<div className="progress-bar_container">
|
||||
<FloatingButton
|
||||
className="layout-progress-bar"
|
||||
icon={primaryProgressBarIcon}
|
||||
percent={primaryProgressBarValue}
|
||||
alert={showPrimaryButtonAlert}
|
||||
onClick={onOpenUploadPanel}
|
||||
clearUploadedFilesHistory={clearUploadedFilesHistory}
|
||||
/>
|
||||
</div>
|
||||
) : !showPrimaryProgressBar && showSecondaryProgressBar ? (
|
||||
<div className="progress-bar_container">
|
||||
<FloatingButton
|
||||
className="layout-progress-bar"
|
||||
icon={secondaryProgressBarIcon}
|
||||
percent={secondaryProgressBarValue}
|
||||
alert={showSecondaryButtonAlert}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</SectionContainer>
|
||||
|
||||
{isInfoPanelAvailable && (
|
||||
<InfoPanel viewAs={viewAs}>
|
||||
<SubInfoPanelHeader>{infoPanelHeaderContent}</SubInfoPanelHeader>
|
||||
<SubInfoPanelBody
|
||||
isInfoPanelScrollLocked={isInfoPanelScrollLocked}
|
||||
>
|
||||
{infoPanelBodyContent}
|
||||
</SubInfoPanelBody>
|
||||
</InfoPanel>
|
||||
)}
|
||||
</Provider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Section.SectionHeader = () => {
|
||||
return null;
|
||||
};
|
||||
Section.SectionHeader.displayName = "SectionHeader";
|
||||
|
||||
Section.SectionFilter = () => {
|
||||
return null;
|
||||
};
|
||||
Section.SectionFilter.displayName = "SectionFilter";
|
||||
|
||||
Section.SectionBody = () => {
|
||||
return null;
|
||||
};
|
||||
Section.SectionBody.displayName = "SectionBody";
|
||||
|
||||
Section.SectionFooter = () => {
|
||||
return null;
|
||||
};
|
||||
Section.SectionFooter.displayName = "SectionFooter";
|
||||
|
||||
Section.SectionPaging = () => {
|
||||
return null;
|
||||
};
|
||||
Section.SectionPaging.displayName = "SectionPaging";
|
||||
|
||||
Section.InfoPanelBody = () => {
|
||||
return null;
|
||||
};
|
||||
Section.InfoPanelBody.displayName = "InfoPanelBody";
|
||||
|
||||
Section.InfoPanelHeader = () => {
|
||||
return null;
|
||||
};
|
||||
Section.InfoPanelHeader.displayName = "InfoPanelHeader";
|
||||
|
||||
Section.SectionWarning = () => {
|
||||
return null;
|
||||
};
|
||||
Section.SectionWarning.displayName = "SectionWarning";
|
||||
|
||||
Section.propTypes = {
|
||||
children: PropTypes.any,
|
||||
withBodyScroll: PropTypes.bool,
|
||||
showPrimaryProgressBar: PropTypes.bool,
|
||||
primaryProgressBarValue: PropTypes.number,
|
||||
showPrimaryButtonAlert: PropTypes.bool,
|
||||
progressBarDropDownContent: PropTypes.any,
|
||||
primaryProgressBarIcon: PropTypes.string,
|
||||
showSecondaryProgressBar: PropTypes.bool,
|
||||
secondaryProgressBarValue: PropTypes.number,
|
||||
secondaryProgressBarIcon: PropTypes.string,
|
||||
showSecondaryButtonAlert: PropTypes.bool,
|
||||
onDrop: PropTypes.func,
|
||||
uploadFiles: PropTypes.bool,
|
||||
viewAs: PropTypes.string,
|
||||
onOpenUploadPanel: PropTypes.func,
|
||||
isTabletView: PropTypes.bool,
|
||||
isHeaderVisible: PropTypes.bool,
|
||||
isInfoPanelAvailable: PropTypes.bool,
|
||||
settingsStudio: PropTypes.bool,
|
||||
isEmptyPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
Section.defaultProps = {
|
||||
withBodyScroll: true,
|
||||
isInfoPanelAvailable: true,
|
||||
settingsStudio: false,
|
||||
};
|
||||
|
||||
export default inject(({ auth }) => {
|
||||
const { settingsStore } = auth;
|
||||
const {
|
||||
isHeaderVisible,
|
||||
isTabletView,
|
||||
maintenanceExist,
|
||||
snackbarExist,
|
||||
showText,
|
||||
currentDeviceType,
|
||||
} = settingsStore;
|
||||
|
||||
const { isScrollLocked: isInfoPanelScrollLocked } = auth.infoPanelStore;
|
||||
|
||||
return {
|
||||
isTabletView,
|
||||
isHeaderVisible,
|
||||
|
||||
maintenanceExist,
|
||||
snackbarExist,
|
||||
|
||||
showText,
|
||||
|
||||
isInfoPanelScrollLocked,
|
||||
currentDeviceType,
|
||||
};
|
||||
})(observer(Section));
|
@ -1,159 +0,0 @@
|
||||
import React from "react";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import styled from "@emotion/styled";
|
||||
import NavMenu from "../NavMenu";
|
||||
import Main from "client/Main";
|
||||
import Section from ".";
|
||||
import history from "../../history";
|
||||
import Headline from "../Headline";
|
||||
import store from "../../store";
|
||||
import { Provider as MobxProvider } from "mobx-react";
|
||||
import { IconButton } from "@docspace/shared/components/icon-button";
|
||||
import { ContextMenuButton } from "@docspace/shared/components/context-menu-button";
|
||||
import { SearchInput } from "@docspace/shared/components";
|
||||
import { Paging } from "@docspace/shared/components";
|
||||
import withReadme from "storybook-readme/with-readme";
|
||||
import { boolean, withKnobs } from "@storybook/addon-knobs/react";
|
||||
import Readme from "./README.md";
|
||||
import { Router } from "react-router-dom";
|
||||
import ActionsHeaderTouchReactSvgUrl from "PUBLIC_DIR/images/actions.header.touch.react.svg?url";
|
||||
|
||||
const { authStore } = store;
|
||||
|
||||
const HeaderContent = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > * {
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const pageItems = [
|
||||
{
|
||||
key: "1",
|
||||
label: "1 of 2",
|
||||
onClick: (e) => action("set paging 1 of 2")(e),
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: "2 of 2",
|
||||
onClick: (e) => action("set paging 2 of 2")(e),
|
||||
},
|
||||
];
|
||||
|
||||
const perPageItems = [
|
||||
{
|
||||
key: "1-1",
|
||||
label: "25 per page",
|
||||
onClick: (e) => action("set paging 25 action")(e),
|
||||
},
|
||||
{
|
||||
key: "1-2",
|
||||
label: "50 per page",
|
||||
onClick: (e) => action("set paging 50 action")(e),
|
||||
},
|
||||
];
|
||||
|
||||
const sectionHeaderContent = (
|
||||
<HeaderContent>
|
||||
<IconButton
|
||||
iconName={"ArrowPathIcon"}
|
||||
size="16"
|
||||
onClick={(e) => action("ArrowPathIcon Clicked")(e)}
|
||||
/>
|
||||
<Headline type="content">Section Header</Headline>
|
||||
<IconButton
|
||||
iconName={ActionsHeaderTouchReactSvgUrl}
|
||||
size="16"
|
||||
onClick={(e) => action("PlusIcon Clicked")(e)}
|
||||
/>
|
||||
<ContextMenuButton
|
||||
title="Actions"
|
||||
getData={() => [
|
||||
{
|
||||
key: "key",
|
||||
label: "label",
|
||||
onClick: (e) => action("label Clicked")(e),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</HeaderContent>
|
||||
);
|
||||
|
||||
const sectionFilterContent = (
|
||||
<SearchInput
|
||||
isNeedFilter={true}
|
||||
getFilterData={() => [
|
||||
{
|
||||
key: "filter-example",
|
||||
group: "filter-example",
|
||||
label: "example group",
|
||||
isHeader: true,
|
||||
},
|
||||
{
|
||||
key: "filter-example-test",
|
||||
group: "filter-example",
|
||||
label: "Test",
|
||||
},
|
||||
]}
|
||||
onSearchClick={(result) => {
|
||||
console.log(result);
|
||||
}}
|
||||
onChangeFilter={(result) => {
|
||||
console.log(result);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const sectionBodyContent = <p style={{ padding: 40 }}>Section Content</p>;
|
||||
|
||||
const sectionPagingContent = (
|
||||
<Paging
|
||||
previousLabel="Previous"
|
||||
nextLabel="Next"
|
||||
pageItems={pageItems}
|
||||
perPageItems={perPageItems}
|
||||
selectedPageItem={pageItems[0]}
|
||||
selectedCountItem={perPageItems[0]}
|
||||
onSelectPage={(a) => console.log(a)}
|
||||
onSelectCount={(a) => console.log(a)}
|
||||
previousAction={(e) => action("Prev Clicked")(e)}
|
||||
nextAction={(e) => action("Next Clicked")(e)}
|
||||
openDirection="top"
|
||||
/>
|
||||
);
|
||||
|
||||
storiesOf("Components|Section", module)
|
||||
.addDecorator(withKnobs)
|
||||
.addDecorator(withReadme(Readme))
|
||||
.add("base", () => (
|
||||
<MobxProvider auth={authStore}>
|
||||
<Router history={history}>
|
||||
<NavMenu
|
||||
isBackdropVisible={boolean("isBackdropVisible", false)}
|
||||
isNavHoverEnabled={boolean("isNavHoverEnabled", true)}
|
||||
isNavOpened={boolean("isNavOpened", false)}
|
||||
isAsideVisible={boolean("isAsideVisible", false)}
|
||||
/>
|
||||
<Main>
|
||||
<Section withBodyScroll={true}>
|
||||
<Section.SectionHeader>
|
||||
{sectionHeaderContent}
|
||||
</Section.SectionHeader>
|
||||
|
||||
<Section.SectionFilter>
|
||||
{sectionFilterContent}
|
||||
</Section.SectionFilter>
|
||||
|
||||
<Section.SectionBody>{sectionBodyContent}</Section.SectionBody>
|
||||
|
||||
<Section.SectionPaging>
|
||||
{sectionPagingContent}
|
||||
</Section.SectionPaging>
|
||||
</Section>
|
||||
</Main>
|
||||
</Router>
|
||||
</MobxProvider>
|
||||
));
|
@ -1,48 +0,0 @@
|
||||
import React from "react";
|
||||
import { mount } from "enzyme";
|
||||
import Section from ".";
|
||||
|
||||
const baseProps = {
|
||||
withBodyScroll: true,
|
||||
withBodyAutoFocus: false,
|
||||
};
|
||||
|
||||
describe("<Section />", () => {
|
||||
it("renders without error", () => {
|
||||
const wrapper = mount(<Section {...baseProps} />);
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("componentDidUpdate() test re-render", () => {
|
||||
const wrapper = mount(<Section {...baseProps} />).instance();
|
||||
|
||||
wrapper.componentDidUpdate({ withBodyScroll: false });
|
||||
|
||||
expect(wrapper.props).toBe(wrapper.props);
|
||||
});
|
||||
|
||||
it("componentDidUpdate() test no re-render", () => {
|
||||
const wrapper = mount(
|
||||
<Section
|
||||
{...baseProps}
|
||||
articleHeaderContent={<>1</>}
|
||||
articleMainButtonContent={<>2</>}
|
||||
articleBodyContent={<>3</>}
|
||||
sectionHeaderContent={<>4</>}
|
||||
sectionFilterContent={<>5</>}
|
||||
sectionBodyContent={<>6</>}
|
||||
sectionPagingContent={<>7</>}
|
||||
withBodyScroll={false}
|
||||
/>
|
||||
).instance();
|
||||
|
||||
wrapper.componentDidUpdate(wrapper.props);
|
||||
|
||||
expect(wrapper.props.withBodyScroll).toBe(false);
|
||||
|
||||
wrapper.componentDidUpdate(wrapper.props);
|
||||
|
||||
expect(wrapper.props).toBe(wrapper.props);
|
||||
});
|
||||
});
|
@ -1,60 +0,0 @@
|
||||
import { useRef } from "react";
|
||||
import { Scrollbar } from "@docspace/shared/components/scrollbar";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
const StyledScrollbar = styled(Scrollbar)`
|
||||
${({ $isScrollLocked }) =>
|
||||
$isScrollLocked &&
|
||||
css`
|
||||
& .scroll-wrapper > .scroller {
|
||||
overflow: hidden !important;
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: -1px !important;
|
||||
`
|
||||
: css`
|
||||
margin-right: -1px !important;
|
||||
`}
|
||||
}
|
||||
${isMobileOnly &&
|
||||
css`
|
||||
& .scroll-wrapper > .scroller {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding-left: 20px !important;
|
||||
margin-left: -21px !important;
|
||||
`
|
||||
: css`
|
||||
padding-right: 20px !important;
|
||||
margin-right: -21px !important;
|
||||
`}
|
||||
}
|
||||
`}
|
||||
`}
|
||||
`;
|
||||
|
||||
const SubInfoPanelBody = ({ children, isInfoPanelScrollLocked }) => {
|
||||
const content = children?.props?.children;
|
||||
const scrollRef = useRef(null);
|
||||
const scrollYPossible = scrollRef?.current?.scrollValues?.scrollYPossible;
|
||||
const scrollLocked = scrollYPossible && isInfoPanelScrollLocked;
|
||||
|
||||
return (
|
||||
<StyledScrollbar
|
||||
ref={scrollRef}
|
||||
$isScrollLocked={scrollLocked}
|
||||
noScrollY={scrollLocked}
|
||||
scrollclass="section-scroll info-panel-scroll"
|
||||
>
|
||||
{content}
|
||||
</StyledScrollbar>
|
||||
);
|
||||
};
|
||||
|
||||
SubInfoPanelBody.displayName = "SubInfoPanelBody";
|
||||
|
||||
export default SubInfoPanelBody;
|
@ -1,10 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const SubInfoPanelHeader = ({ children }) => {
|
||||
const content = children?.props?.children;
|
||||
return <>{content}</>;
|
||||
};
|
||||
|
||||
SubInfoPanelHeader.displayName = "SubInfoPanelHeader";
|
||||
|
||||
export default SubInfoPanelHeader;
|
@ -1,254 +0,0 @@
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
import { tablet, mobile } from "@docspace/shared/utils";
|
||||
import { INFO_PANEL_WIDTH } from "@docspace/shared/utils";
|
||||
import { isMobileOnly, isIOS } from "react-device-detect";
|
||||
import { inject } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useEffect, useState, useRef, useCallback } from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import CrossIcon from "PUBLIC_DIR/images/icons/17/cross.react.svg";
|
||||
|
||||
import { Portal } from "@docspace/shared/components/portal";
|
||||
import { DeviceType } from "@docspace/shared/enums";
|
||||
|
||||
const StyledInfoPanelWrapper = styled.div.attrs(({ id }) => ({
|
||||
id: id,
|
||||
}))`
|
||||
user-select: none;
|
||||
height: auto;
|
||||
width: auto;
|
||||
background: ${(props) => props.theme.infoPanel.blurColor};
|
||||
backdrop-filter: blur(3px);
|
||||
z-index: 300;
|
||||
@media ${tablet} {
|
||||
z-index: 309;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledInfoPanel = styled.div`
|
||||
height: 100%;
|
||||
width: ${INFO_PANEL_WIDTH}px;
|
||||
background-color: ${(props) => props.theme.infoPanel.backgroundColor};
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
border-right: ${(props) =>
|
||||
`1px solid ${props.theme.infoPanel.borderColor}`};
|
||||
`
|
||||
: css`
|
||||
border-left: ${(props) =>
|
||||
`1px solid ${props.theme.infoPanel.borderColor}`};
|
||||
`}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.scroll-body {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
@media ${tablet} {
|
||||
position: absolute;
|
||||
border: none;
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
left: 0;
|
||||
`
|
||||
: css`
|
||||
right: 0;
|
||||
`}
|
||||
width: 480px;
|
||||
max-width: calc(100vw - 69px);
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
bottom: 0;
|
||||
height: calc(100% - 64px);
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledControlContainer = styled.div`
|
||||
display: none;
|
||||
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
position: absolute;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 450;
|
||||
|
||||
@media ${tablet} {
|
||||
display: flex;
|
||||
|
||||
top: 18px;
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
right: -27px;
|
||||
`
|
||||
: css`
|
||||
left: -27px;
|
||||
`}
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
display: flex;
|
||||
|
||||
top: -27px;
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
left: 10px;
|
||||
`
|
||||
: css`
|
||||
right: 10px;
|
||||
`}
|
||||
left: unset;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledControlContainer.defaultProps = { theme: Base };
|
||||
|
||||
const StyledCrossIcon = styled(CrossIcon)`
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
z-index: 455;
|
||||
path {
|
||||
stroke: ${(props) => props.theme.catalog.control.fill};
|
||||
}
|
||||
`;
|
||||
|
||||
StyledCrossIcon.defaultProps = { theme: Base };
|
||||
|
||||
const InfoPanel = ({
|
||||
children,
|
||||
isVisible,
|
||||
isMobileHidden,
|
||||
setIsVisible,
|
||||
canDisplay,
|
||||
anotherDialogOpen,
|
||||
viewAs,
|
||||
currentDeviceType,
|
||||
}) => {
|
||||
const infoPanelRef = useRef(null);
|
||||
|
||||
const closeInfoPanel = () => setIsVisible(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onMouseDown = (e) => {
|
||||
if (e.target.id === "InfoPanelWrapper") closeInfoPanel();
|
||||
};
|
||||
|
||||
if (viewAs === "row" || currentDeviceType !== DeviceType.desktop)
|
||||
document.addEventListener("mousedown", onMouseDown);
|
||||
|
||||
window.onpopstate = () => {
|
||||
if (currentDeviceType !== DeviceType.desktop && isVisible)
|
||||
closeInfoPanel();
|
||||
};
|
||||
|
||||
return () => document.removeEventListener("mousedown", onMouseDown);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobileOnly && isIOS) {
|
||||
window.visualViewport.addEventListener("resize", onResize);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.visualViewport.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onResize = useCallback((e) => {
|
||||
if (infoPanelRef?.current)
|
||||
infoPanelRef.current.style.height = `${e.target.height}px`;
|
||||
}, []);
|
||||
|
||||
const infoPanelComponent = (
|
||||
<StyledInfoPanelWrapper
|
||||
isRowView={viewAs === "row"}
|
||||
className="info-panel"
|
||||
id="InfoPanelWrapper"
|
||||
ref={infoPanelRef}
|
||||
>
|
||||
<StyledInfoPanel isRowView={viewAs === "row"}>
|
||||
<StyledControlContainer
|
||||
isRowView={viewAs === "row"}
|
||||
onClick={closeInfoPanel}
|
||||
>
|
||||
<StyledCrossIcon />
|
||||
</StyledControlContainer>
|
||||
|
||||
{children}
|
||||
</StyledInfoPanel>
|
||||
</StyledInfoPanelWrapper>
|
||||
);
|
||||
|
||||
const renderPortalInfoPanel = () => {
|
||||
const rootElement = document.getElementById("root");
|
||||
|
||||
return (
|
||||
<Portal
|
||||
element={infoPanelComponent}
|
||||
appendTo={rootElement}
|
||||
visible={isVisible && !isMobileHidden && !anotherDialogOpen}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return !isVisible ||
|
||||
!canDisplay ||
|
||||
(anotherDialogOpen && currentDeviceType !== DeviceType.desktop) ||
|
||||
(currentDeviceType !== DeviceType.desktop && isMobileHidden)
|
||||
? null
|
||||
: currentDeviceType === DeviceType.mobile
|
||||
? renderPortalInfoPanel()
|
||||
: infoPanelComponent;
|
||||
};
|
||||
|
||||
InfoPanel.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.any,
|
||||
]),
|
||||
isVisible: PropTypes.bool,
|
||||
};
|
||||
|
||||
StyledInfoPanelWrapper.defaultProps = { theme: Base };
|
||||
StyledInfoPanel.defaultProps = { theme: Base };
|
||||
InfoPanel.defaultProps = { theme: Base };
|
||||
|
||||
export default inject(({ auth, dialogsStore }) => {
|
||||
const { isVisible, isMobileHidden, setIsVisible, getCanDisplay } =
|
||||
auth.infoPanelStore;
|
||||
|
||||
const { currentDeviceType } = auth.settingsStore;
|
||||
|
||||
const { createRoomDialogVisible, invitePanelOptions } = dialogsStore;
|
||||
|
||||
const canDisplay = getCanDisplay();
|
||||
|
||||
const anotherDialogOpen =
|
||||
createRoomDialogVisible || invitePanelOptions.visible;
|
||||
|
||||
return {
|
||||
isVisible,
|
||||
isMobileHidden,
|
||||
setIsVisible,
|
||||
canDisplay,
|
||||
anotherDialogOpen,
|
||||
currentDeviceType,
|
||||
};
|
||||
})(InfoPanel);
|
@ -1,23 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import equal from "fast-deep-equal/react";
|
||||
|
||||
class SectionBodyContent extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !equal(this.props, nextProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
//console.log(" SectionBodyContent render");
|
||||
return <>{children}</>;
|
||||
}
|
||||
}
|
||||
|
||||
SectionBodyContent.displayName = "SectionBodyContent";
|
||||
|
||||
SectionBodyContent.propTypes = {
|
||||
children: PropTypes.any,
|
||||
};
|
||||
|
||||
export default SectionBodyContent;
|
@ -1,444 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled, { css } from "styled-components";
|
||||
//import equal from "fast-deep-equal/react";
|
||||
//import { LayoutContextConsumer } from "client/Layout/context";
|
||||
// import { isMobile, isMobileOnly } from "react-device-detect";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
import { Scrollbar } from "@docspace/shared/components/scrollbar";
|
||||
import DragAndDrop from "@docspace/shared/components/drag-and-drop/DragAndDrop";
|
||||
import { tablet, desktop, mobile, mobileMore } from "@docspace/shared/utils";
|
||||
import { DeviceType } from "@docspace/shared/enums";
|
||||
|
||||
const settingsStudioStyles = css`
|
||||
${({ settingsStudio }) =>
|
||||
settingsStudio &&
|
||||
css`
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 0 20px 16px 7px;
|
||||
`
|
||||
: css`
|
||||
padding: 0 7px 16px 20px;
|
||||
`}
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 0 24px 16px 0;
|
||||
`
|
||||
: css`
|
||||
padding: 0 0 16px 24px;
|
||||
`}
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 0 24px 16px 0;
|
||||
`
|
||||
: css`
|
||||
padding: 0 0 16px 24px;
|
||||
`}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const paddingStyles = css`
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 19px 20px 16px 3px;
|
||||
`
|
||||
: css`
|
||||
padding: 19px 3px 16px 20px;
|
||||
`}
|
||||
outline: none;
|
||||
|
||||
${settingsStudioStyles};
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 0px 24px 16px 0;
|
||||
`
|
||||
: css`
|
||||
padding: 0px 0 16px 24px;
|
||||
`}
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 0px 24px 16px 8px;
|
||||
`
|
||||
: css`
|
||||
padding: 0px 8px 16px 24px;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
const commonStyles = css`
|
||||
flex-grow: 1;
|
||||
|
||||
${(props) => (props.isDesktop ? "height: auto" : "height: 100%")};
|
||||
|
||||
${(props) => !props.withScroll && `height: 100%;`}
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
|
||||
.section-wrapper {
|
||||
height: 100%;
|
||||
${(props) =>
|
||||
!props.withScroll &&
|
||||
css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
`};
|
||||
${(props) => !props.withScroll && paddingStyles}
|
||||
}
|
||||
|
||||
.section-wrapper-content {
|
||||
${paddingStyles}
|
||||
flex: 1 0 auto;
|
||||
outline: none;
|
||||
${(props) =>
|
||||
props.viewAs == "tile" &&
|
||||
css`
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding-right: 20px;
|
||||
`
|
||||
: css`
|
||||
padding-left: 20px;
|
||||
`}
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
(props.viewAs == "settings" || props.viewAs == "profile") &&
|
||||
css`
|
||||
padding-top: 0;
|
||||
|
||||
@media ${tablet} {
|
||||
padding-top: 0;
|
||||
}
|
||||
`};
|
||||
|
||||
@media ${`${mobileMore} and ${tablet}`} {
|
||||
${({ isFormGallery, theme }) =>
|
||||
isFormGallery &&
|
||||
css`
|
||||
padding: ${theme.interfaceDirection === "rtl"
|
||||
? "0 16px 20px 0"
|
||||
: "0 0 20px 16px"} !important;
|
||||
`}
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
${({ isFormGallery, theme }) =>
|
||||
isFormGallery &&
|
||||
css`
|
||||
padding: ${theme.interfaceDirection === "rtl"
|
||||
? "0px 16px 16px 16px"
|
||||
: "0px 16px 16px 16px"} !important;
|
||||
`}
|
||||
}
|
||||
|
||||
.section-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.files-tile-container {
|
||||
@media ${desktop} {
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.people-row-container,
|
||||
.files-row-container {
|
||||
margin-top: 0px;
|
||||
|
||||
@media ${desktop} {
|
||||
margin-top: -17px;
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
@media ${desktop} {
|
||||
${(props) =>
|
||||
props.viewAs === "row" &&
|
||||
css`
|
||||
margin-top: -15px;
|
||||
`}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSectionBody = styled.div`
|
||||
max-width: 100vw !important;
|
||||
user-select: none;
|
||||
|
||||
${commonStyles};
|
||||
|
||||
${(props) =>
|
||||
props.withScroll &&
|
||||
css`
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: -20px;
|
||||
`
|
||||
: css`
|
||||
margin-left: -20px;
|
||||
`}
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: -24px;
|
||||
`
|
||||
: css`
|
||||
margin-left: -24px;
|
||||
`}
|
||||
}
|
||||
`}
|
||||
|
||||
${({ isFormGallery }) =>
|
||||
isFormGallery &&
|
||||
css`
|
||||
@media ${tablet} {
|
||||
margin: ${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? "0 -16px 0 0 "
|
||||
: "0 0 0 -16px"};
|
||||
|
||||
padding: ${(props) =>
|
||||
props.theme.interfaceDirection === "rtl" ? "0 0 0 0 " : "0 0 0 0"};
|
||||
}
|
||||
`}
|
||||
|
||||
.additional-scroll-height {
|
||||
${({ withScroll }) =>
|
||||
!withScroll &&
|
||||
css`
|
||||
height: 64px;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDropZoneBody = styled(DragAndDrop)`
|
||||
max-width: 100vw !important;
|
||||
|
||||
${commonStyles};
|
||||
|
||||
.drag-and-drop {
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.withScroll &&
|
||||
css`
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: -20px;
|
||||
`
|
||||
: css`
|
||||
margin-left: -20px;
|
||||
`}
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: -24px;
|
||||
`
|
||||
: css`
|
||||
margin-left: -24px;
|
||||
`}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledSpacer = styled.div`
|
||||
display: none;
|
||||
min-height: 64px;
|
||||
|
||||
@media ${tablet} {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
class SectionBody extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.focusRef = React.createRef();
|
||||
}
|
||||
|
||||
// shouldComponentUpdate(nextProps) {
|
||||
// return !equal(this.props, nextProps);
|
||||
// }
|
||||
|
||||
componentDidMount() {
|
||||
const { withScroll } = this.props;
|
||||
if (!this.props.autoFocus) return;
|
||||
if (withScroll) this?.focusRef?.current?.focus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.focusRef = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
//console.log(" SectionBody render" );
|
||||
const {
|
||||
isFormGallery,
|
||||
autoFocus,
|
||||
children,
|
||||
onDrop,
|
||||
uploadFiles,
|
||||
viewAs,
|
||||
withScroll,
|
||||
isLoaded,
|
||||
isDesktop,
|
||||
settingsStudio,
|
||||
currentDeviceType,
|
||||
} = this.props;
|
||||
|
||||
const focusProps = autoFocus
|
||||
? {
|
||||
ref: this.focusRef,
|
||||
tabIndex: -1,
|
||||
}
|
||||
: {};
|
||||
|
||||
return uploadFiles ? (
|
||||
<StyledDropZoneBody
|
||||
isDropZone
|
||||
onDrop={onDrop}
|
||||
withScroll={withScroll}
|
||||
viewAs={viewAs}
|
||||
isLoaded={isLoaded}
|
||||
isDesktop={isDesktop}
|
||||
settingsStudio={settingsStudio}
|
||||
className="section-body"
|
||||
>
|
||||
{withScroll ? (
|
||||
currentDeviceType !== DeviceType.mobile ? (
|
||||
<Scrollbar
|
||||
id="sectionScroll"
|
||||
scrollclass="section-scroll"
|
||||
fixedSize
|
||||
>
|
||||
<div className="section-wrapper">
|
||||
<div className="section-wrapper-content" {...focusProps}>
|
||||
{children}
|
||||
<StyledSpacer />
|
||||
</div>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
) : (
|
||||
<div className="section-wrapper">
|
||||
<div className="section-wrapper-content" {...focusProps}>
|
||||
{children}
|
||||
<StyledSpacer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="section-wrapper">
|
||||
{children}
|
||||
<StyledSpacer />
|
||||
</div>
|
||||
)}
|
||||
</StyledDropZoneBody>
|
||||
) : (
|
||||
<StyledSectionBody
|
||||
viewAs={viewAs}
|
||||
withScroll={withScroll}
|
||||
isLoaded={isLoaded}
|
||||
isDesktop={isDesktop}
|
||||
settingsStudio={settingsStudio}
|
||||
isFormGallery={isFormGallery}
|
||||
>
|
||||
{withScroll ? (
|
||||
currentDeviceType !== DeviceType.mobile ? (
|
||||
<Scrollbar
|
||||
id="sectionScroll"
|
||||
scrollclass="section-scroll"
|
||||
fixedSize
|
||||
>
|
||||
<div className="section-wrapper">
|
||||
<div className="section-wrapper-content" {...focusProps}>
|
||||
{children}
|
||||
<StyledSpacer className="settings-mobile" />
|
||||
</div>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
) : (
|
||||
<div className="section-wrapper">
|
||||
<div className="section-wrapper-content" {...focusProps}>
|
||||
{children}
|
||||
<StyledSpacer className="settings-mobile" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="section-wrapper">{children}</div>
|
||||
)}
|
||||
</StyledSectionBody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SectionBody.displayName = "SectionBody";
|
||||
|
||||
SectionBody.propTypes = {
|
||||
withScroll: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool,
|
||||
onDrop: PropTypes.func,
|
||||
uploadFiles: PropTypes.bool,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.any,
|
||||
]),
|
||||
viewAs: PropTypes.string,
|
||||
isLoaded: PropTypes.bool,
|
||||
settingsStudio: PropTypes.bool,
|
||||
};
|
||||
|
||||
SectionBody.defaultProps = {
|
||||
autoFocus: false,
|
||||
uploadFiles: false,
|
||||
withScroll: true,
|
||||
settingsStudio: false,
|
||||
};
|
||||
|
||||
export default inject(({ auth }) => {
|
||||
const { settingsStore } = auth;
|
||||
const { isDesktopClient: isDesktop, currentDeviceType } = settingsStore;
|
||||
return {
|
||||
isLoaded: auth.isLoaded,
|
||||
isDesktop,
|
||||
currentDeviceType,
|
||||
};
|
||||
})(observer(SectionBody));
|
@ -1,153 +0,0 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { tablet, mobile } from "@docspace/shared/utils";
|
||||
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
|
||||
const tabletProps = css`
|
||||
.section-body_header {
|
||||
width: 100%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: ${(props) =>
|
||||
props.viewAs === "profile" || props.viewAs === "settings"
|
||||
? props.theme.section.header.backgroundColor
|
||||
: props.theme.section.header.background};
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding-left: 0;
|
||||
`
|
||||
: css`
|
||||
padding-right: 0;
|
||||
`}
|
||||
z-index: 201;
|
||||
}
|
||||
.section-body_filter {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const closeIconSize = "24px";
|
||||
const sizeBetweenIcons = "8px";
|
||||
|
||||
const StyledSectionContainer = styled.section`
|
||||
position: relative;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 0 20px 0 0;
|
||||
`
|
||||
: css`
|
||||
padding: 0 0 0 20px;
|
||||
`}
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
@media ${tablet} {
|
||||
width: 100%;
|
||||
max-width: 100vw !important;
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding: 0 16px 0 0;
|
||||
`
|
||||
: css`
|
||||
padding: 0 0 0 16px;
|
||||
`}
|
||||
${tabletProps};
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
}
|
||||
|
||||
.progress-bar_container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
display: grid;
|
||||
grid-gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: 24px;
|
||||
left: 0;
|
||||
`
|
||||
: css`
|
||||
margin-right: 24px;
|
||||
right: 0;
|
||||
`}
|
||||
|
||||
.layout-progress-bar_wrapper {
|
||||
position: static;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
|
||||
.layout-progress-bar,
|
||||
.layout-progress-second-bar {
|
||||
${(props) =>
|
||||
props.showTwoProgress &&
|
||||
css`
|
||||
${props.theme.interfaceDirection === "rtl"
|
||||
? `margin-right:calc(${closeIconSize} + ${sizeBetweenIcons}); `
|
||||
: `margin-left:calc(${closeIconSize} + ${sizeBetweenIcons})`}
|
||||
`}
|
||||
}
|
||||
|
||||
.layout-progress-bar_close-icon {
|
||||
position: static;
|
||||
width: ${closeIconSize};
|
||||
height: ${closeIconSize};
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: ${sizeBetweenIcons};
|
||||
`
|
||||
: css`
|
||||
margin-right: ${sizeBetweenIcons};
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.showTwoProgress &&
|
||||
css`
|
||||
${props.theme.interfaceDirection === "rtl"
|
||||
? `margin-left:-${closeIconSize}`
|
||||
: `margin-right:-${closeIconSize}`}
|
||||
`}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
!props.isSectionHeaderAvailable &&
|
||||
css`
|
||||
width: 100vw !important;
|
||||
max-width: 100vw !important;
|
||||
box-sizing: border-box;
|
||||
`}
|
||||
`;
|
||||
|
||||
StyledSectionContainer.defaultProps = { theme: Base };
|
||||
|
||||
const SectionContainer = React.forwardRef((props, forwardRef) => {
|
||||
return <StyledSectionContainer ref={forwardRef} id="section" {...props} />;
|
||||
});
|
||||
|
||||
export default SectionContainer;
|
@ -1,41 +0,0 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import equal from "fast-deep-equal/react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { tablet, desktop } from "@docspace/shared/utils";
|
||||
|
||||
const StyledSectionFilter = styled.div`
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: 20px;
|
||||
`
|
||||
: css`
|
||||
margin-right: 20px;
|
||||
`}
|
||||
@media ${tablet} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: 16px;
|
||||
`
|
||||
: css`
|
||||
margin-right: 16px;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
class SectionFilter extends React.Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !equal(this.props, nextProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
//console.log("PageLayout SectionFilter render");
|
||||
return <StyledSectionFilter className="section-filter" {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
SectionFilter.displayName = "SectionFilter";
|
||||
|
||||
export default SectionFilter;
|
@ -1,19 +0,0 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { mobile } from "@docspace/shared/utils";
|
||||
|
||||
const StyledSectionFooter = styled.div`
|
||||
margin-top: 40px;
|
||||
|
||||
@media ${mobile} {
|
||||
margin-top: 32px;
|
||||
}
|
||||
`;
|
||||
|
||||
const SectionFooter = ({ children }) => {
|
||||
return <StyledSectionFooter>{children}</StyledSectionFooter>;
|
||||
};
|
||||
|
||||
SectionFooter.displayName = "SectionFooter";
|
||||
|
||||
export default SectionFooter;
|
@ -1,119 +0,0 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import PropTypes from "prop-types";
|
||||
import { tablet, mobile } from "@docspace/shared/utils";
|
||||
import { NoUserSelect } from "@docspace/shared/utils";
|
||||
|
||||
import Base from "@docspace/shared/themes/base";
|
||||
|
||||
const StyledSectionHeader = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
height: 69px;
|
||||
min-height: 69px;
|
||||
|
||||
@media ${tablet} {
|
||||
height: 61px;
|
||||
min-height: 61px;
|
||||
|
||||
${({ isFormGallery }) =>
|
||||
isFormGallery &&
|
||||
css`
|
||||
height: 69px;
|
||||
min-height: 69px;
|
||||
`}
|
||||
|
||||
.header-container {
|
||||
margin-bottom: 1px;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
height: 53px;
|
||||
min-height: 53px;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding-left: 20px;
|
||||
`
|
||||
: css`
|
||||
padding-right: 20px;
|
||||
`}
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
${NoUserSelect}
|
||||
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding-left: 16px;
|
||||
margin-left: 0px;
|
||||
`
|
||||
: css`
|
||||
padding-right: 16px;
|
||||
margin-right: 0px;
|
||||
`}
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: 0px;
|
||||
`
|
||||
: css`
|
||||
margin-right: 0px;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSectionHeader.defaultProps = { theme: Base };
|
||||
|
||||
const SectionHeader = (props) => {
|
||||
const {
|
||||
viewAs,
|
||||
settingsStudio = false,
|
||||
className,
|
||||
isEmptyPage,
|
||||
isTrashFolder,
|
||||
isFormGallery,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<StyledSectionHeader
|
||||
className={`section-header ${className}`}
|
||||
isEmptyPage={isEmptyPage}
|
||||
viewAs={viewAs}
|
||||
settingsStudio={settingsStudio}
|
||||
isTrashFolder={isTrashFolder}
|
||||
isFormGallery={isFormGallery}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
SectionHeader.displayName = "SectionHeader";
|
||||
|
||||
SectionHeader.propTypes = {
|
||||
isArticlePinned: PropTypes.bool,
|
||||
isHeaderVisible: PropTypes.bool,
|
||||
settingsStudio: PropTypes.bool,
|
||||
};
|
||||
export default SectionHeader;
|
@ -1,37 +0,0 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import equal from "fast-deep-equal/react";
|
||||
import { tablet } from "@docspace/shared/utils";
|
||||
|
||||
const StyledSectionPaging = styled.div`
|
||||
margin: 16px 0 0;
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding-left: 3px;
|
||||
`
|
||||
: css`
|
||||
padding-right: 3px;
|
||||
`}
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
padding-left: 0px;
|
||||
`
|
||||
: css`
|
||||
padding-right: 0px;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
class SectionPaging extends React.Component {
|
||||
render() {
|
||||
return <StyledSectionPaging {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
SectionPaging.displayName = "SectionPaging";
|
||||
|
||||
export default SectionPaging;
|
@ -1,9 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const SectionWarning = ({ children }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
|
||||
SectionWarning.displayName = "SectionWarning";
|
||||
|
||||
export default SectionWarning;
|
@ -70,7 +70,6 @@ const SectionWrapper = ({
|
||||
|
||||
export default inject(
|
||||
({ auth, dialogsStore }: { auth: any; dialogsStore: any }) => {
|
||||
console.log(auth, dialogsStore);
|
||||
const { settingsStore } = auth;
|
||||
const {
|
||||
isDesktopClient: isDesktop,
|
||||
|
@ -10,6 +10,10 @@ const StyledHeader = styled.div`
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
width: calc(100% - 128px);
|
||||
`;
|
||||
|
||||
const SimpleHeader = () => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { TUser } from "types";
|
||||
import { TCreatedBy, TPathParts } from "../../types";
|
||||
import { TUser } from "../people/types";
|
||||
import {
|
||||
EmployeeActivationStatus,
|
||||
EmployeeStatus,
|
||||
@ -9,14 +10,6 @@ import {
|
||||
ShareAccessRights,
|
||||
} from "../../enums";
|
||||
|
||||
export type TCreatedBy = {
|
||||
avatarSmall: string;
|
||||
displayName: string;
|
||||
hasAvatar: boolean;
|
||||
id: string;
|
||||
profileUrl: string;
|
||||
};
|
||||
|
||||
export type TFileViewAccessibility = {
|
||||
CanConvert: boolean;
|
||||
CoAuhtoring: boolean;
|
||||
@ -148,12 +141,6 @@ export type TFolder = {
|
||||
|
||||
export type TGetFolderPath = TFolder[];
|
||||
|
||||
export type TPathParts = {
|
||||
id: number;
|
||||
title: string;
|
||||
roomType?: RoomsType;
|
||||
};
|
||||
|
||||
export type TGetFolder = {
|
||||
files: TFile[];
|
||||
folders: TFolder[];
|
||||
|
@ -1,36 +1,36 @@
|
||||
import { request } from "../client";
|
||||
// import axios from "axios";
|
||||
import Filter from "./filter";
|
||||
import * as fakePeople from "./fake";
|
||||
|
||||
import { Encoder } from "../../utils/encoder";
|
||||
import { checkFilterInstance } from "../../utils/common";
|
||||
import { TGetUserList } from "./types";
|
||||
|
||||
export function getUserList(filter = Filter.getDefault(), fake = false) {
|
||||
export async function getUserList(filter = Filter.getDefault()) {
|
||||
let params = "";
|
||||
if (fake) {
|
||||
return fakePeople.getUserList(filter);
|
||||
}
|
||||
// if (fake) {
|
||||
// return fakePeople.getUserList(filter);
|
||||
// }
|
||||
|
||||
if (filter) {
|
||||
checkFilterInstance(filter, Filter);
|
||||
|
||||
params = `/filter?${filter.toApiUrlParams(
|
||||
"id,status,isAdmin,isOwner,isRoomAdmin,isVisitor,activationStatus,userName,email,mobilePhone,displayName,avatar,listAdminModules,birthday,title,location,isLDAP,isSSO,groups",
|
||||
)}`;
|
||||
params = `/filter?${filter.toApiUrlParams()}`;
|
||||
}
|
||||
|
||||
return request({
|
||||
const res = (await request({
|
||||
method: "get",
|
||||
url: `/people${params}`,
|
||||
}).then((res) => {
|
||||
res.items = res.items.map((user) => {
|
||||
if (user && user.displayName) {
|
||||
user.displayName = Encoder.htmlDecode(user.displayName);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
return res;
|
||||
})) as TGetUserList;
|
||||
|
||||
res.items = res.items.map((user) => {
|
||||
if (user && user.displayName) {
|
||||
user.displayName = Encoder.htmlDecode(user.displayName);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getUser(userName = null, headers = null) {
|
43
packages/shared/api/people/types.ts
Normal file
43
packages/shared/api/people/types.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
EmployeeActivationStatus,
|
||||
EmployeeStatus,
|
||||
ThemeKeys,
|
||||
} from "../../enums";
|
||||
|
||||
export type TUser = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
userName: string;
|
||||
email: string;
|
||||
status: EmployeeStatus;
|
||||
activationStatus: EmployeeActivationStatus;
|
||||
department: string;
|
||||
workFrom: string;
|
||||
avatarMax: string;
|
||||
avatarMedium: string;
|
||||
avatar: string;
|
||||
isAdmin: boolean;
|
||||
isRoomAdmin: boolean;
|
||||
isLDAP: boolean;
|
||||
listAdminModules: string[];
|
||||
isOwner: boolean;
|
||||
isVisitor: boolean;
|
||||
isCollaborator: boolean;
|
||||
mobilePhoneActivationStatus: number;
|
||||
isSSO: boolean;
|
||||
quotaLimit: number;
|
||||
usedSpace: number;
|
||||
id: string;
|
||||
displayName: string;
|
||||
avatarSmall: string;
|
||||
profileUrl: string;
|
||||
hasAvatar: boolean;
|
||||
theme?: ThemeKeys;
|
||||
mobilePhone?: string;
|
||||
cultureName?: string;
|
||||
};
|
||||
|
||||
export type TGetUserList = {
|
||||
items: TUser[];
|
||||
total: number;
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
import { AxiosRequestConfig } from "axios";
|
||||
|
||||
import { FolderType } from "../../enums";
|
||||
import { request } from "../client";
|
||||
import {
|
||||
@ -6,8 +8,9 @@ import {
|
||||
toUrlParams,
|
||||
} from "../../utils/common";
|
||||
import RoomsFilter from "./filter";
|
||||
import { TGetRooms } from "./types";
|
||||
|
||||
export function getRooms(filter, signal) {
|
||||
export async function getRooms(filter: RoomsFilter, signal?: AbortSignal) {
|
||||
let params;
|
||||
|
||||
if (filter) {
|
||||
@ -16,24 +19,24 @@ export function getRooms(filter, signal) {
|
||||
params = `?${filter.toApiUrlParams()}`;
|
||||
}
|
||||
|
||||
const options = {
|
||||
const options: AxiosRequestConfig = {
|
||||
method: "get",
|
||||
url: `/files/rooms${params}`,
|
||||
signal,
|
||||
};
|
||||
|
||||
return request(options).then((res) => {
|
||||
res.files = decodeDisplayName(res.files);
|
||||
res.folders = decodeDisplayName(res.folders);
|
||||
const res = (await request(options)) as TGetRooms;
|
||||
|
||||
if (res.current.rootFolderType === FolderType.Archive) {
|
||||
res.folders.forEach((room) => {
|
||||
room.isArchive = true;
|
||||
});
|
||||
}
|
||||
res.files = decodeDisplayName(res.files);
|
||||
res.folders = decodeDisplayName(res.folders);
|
||||
|
||||
return res;
|
||||
});
|
||||
if (res.current.rootFolderType === FolderType.Archive) {
|
||||
res.folders.forEach((room) => {
|
||||
room.isArchive = true;
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export function getRoomInfo(id) {
|
66
packages/shared/api/rooms/types.ts
Normal file
66
packages/shared/api/rooms/types.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { TFile, TFolder } from "../files/types";
|
||||
import { FolderType, RoomsType, ShareAccessRights } from "../../enums";
|
||||
import { TCreatedBy, TPathParts } from "../../types";
|
||||
|
||||
export type TLogo = {
|
||||
original: string;
|
||||
large: string;
|
||||
medium: string;
|
||||
small: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export type TRoomSecurity = {
|
||||
Read: boolean;
|
||||
Create: boolean;
|
||||
Delete: boolean;
|
||||
EditRoom: boolean;
|
||||
Rename: boolean;
|
||||
CopyTo: boolean;
|
||||
Copy: boolean;
|
||||
MoveTo: boolean;
|
||||
Move: boolean;
|
||||
Pin: boolean;
|
||||
Mute: boolean;
|
||||
EditAccess: boolean;
|
||||
Duplicate: boolean;
|
||||
Download: boolean;
|
||||
CopySharedLink: boolean;
|
||||
};
|
||||
|
||||
export type TRoom = {
|
||||
parentId: number;
|
||||
filesCount: number;
|
||||
foldersCount: number;
|
||||
new: number;
|
||||
mute: boolean;
|
||||
tags: string[];
|
||||
logo: TLogo;
|
||||
pinned: boolean;
|
||||
roomType: RoomsType;
|
||||
private: boolean;
|
||||
inRoom: boolean;
|
||||
id: number;
|
||||
rootFolderId: number;
|
||||
canShare: boolean;
|
||||
title: string;
|
||||
access: ShareAccessRights;
|
||||
shared: boolean;
|
||||
created: Date;
|
||||
createdBy: TCreatedBy;
|
||||
updated: Date;
|
||||
rootFolderType: FolderType;
|
||||
updatedBy: TCreatedBy;
|
||||
isArchive?: boolean;
|
||||
};
|
||||
|
||||
export type TGetRooms = {
|
||||
files: TFile[];
|
||||
folders: TRoom[];
|
||||
current: TFolder;
|
||||
pathParts: TPathParts[];
|
||||
startIndex: number;
|
||||
count: number;
|
||||
total: number;
|
||||
new: number;
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
export const enum AvatarRole {
|
||||
export enum AvatarRole {
|
||||
owner = "owner",
|
||||
admin = "admin",
|
||||
guest = "guest",
|
||||
|
@ -14,6 +14,9 @@ export interface DefaultColorThemeProps {
|
||||
|
||||
export interface FilterBlockItemTagColorTheme extends DefaultColorThemeProps {
|
||||
themeId: ThemeId.FilterBlockItemTag;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
id?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface IconButtonColorTheme
|
||||
|
@ -16,6 +16,7 @@ export type TOption = {
|
||||
description?: string;
|
||||
quota?: "free" | "paid";
|
||||
isSeparator?: boolean;
|
||||
isSelected?: boolean;
|
||||
};
|
||||
|
||||
export interface ComboboxProps {
|
||||
|
@ -1,12 +1,132 @@
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
import { ToggleButton } from "@docspace/shared/components/toggle-button";
|
||||
import { tablet, mobile } from "@docspace/shared/utils";
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
import CrossIcon from "PUBLIC_DIR/images/cross.react.svg";
|
||||
|
||||
const StyledFilterBlock = styled.div`
|
||||
import { tablet, mobile } from "../../utils";
|
||||
import { Base } from "../../themes";
|
||||
import { TViewAs } from "../../types";
|
||||
|
||||
import { SearchInput } from "../search-input";
|
||||
import { ToggleButton } from "../toggle-button";
|
||||
import { Text } from "../text";
|
||||
|
||||
const StyledFilterInput = styled.div`
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.filter-input_filter-row {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.filter-input_selected-row {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
margin-bottom: 8px;
|
||||
|
||||
.clear-all-link {
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: 12px;
|
||||
`
|
||||
: css`
|
||||
margin-left: 12px;
|
||||
`}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SearchInput)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledButton = styled.div<{ isOpen: boolean }>`
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
|
||||
position: relative;
|
||||
|
||||
border: ${(props) => props.theme.filterInput.button.border};
|
||||
border-radius: 3px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: 8px;
|
||||
`
|
||||
: css`
|
||||
margin-left: 8px;
|
||||
`}
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border: ${(props) => props.theme.filterInput.button.hoverBorder};
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.iconButton.hoverColor};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.isOpen &&
|
||||
css`
|
||||
background: ${props.theme.filterInput.button.openBackground};
|
||||
pointer-events: none;
|
||||
|
||||
svg {
|
||||
path {
|
||||
fill: ${props.theme.filterInput.button.openFill};
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-container {
|
||||
margin-top: 5px;
|
||||
min-width: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
`}
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
`;
|
||||
|
||||
StyledButton.defaultProps = { theme: Base };
|
||||
|
||||
const StyledFilterBlock = styled.div<{ showFooter?: boolean }>`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
@ -72,7 +192,7 @@ const StyledFilterBlock = styled.div`
|
||||
|
||||
StyledFilterBlock.defaultProps = { theme: Base };
|
||||
|
||||
const StyledFilterBlockHeader = styled.div`
|
||||
const StyledFilterBlockHeader = styled.div<{ isSelector?: boolean }>`
|
||||
height: 53px;
|
||||
min-height: 53px;
|
||||
|
||||
@ -110,7 +230,10 @@ const StyledFilterBlockHeader = styled.div`
|
||||
|
||||
StyledFilterBlockHeader.defaultProps = { theme: Base };
|
||||
|
||||
const StyledFilterBlockItem = styled.div`
|
||||
const StyledFilterBlockItem = styled.div<{
|
||||
withoutHeader: boolean;
|
||||
isFirst?: boolean;
|
||||
}>`
|
||||
margin: ${(props) =>
|
||||
props.withoutHeader ? "0" : props.isFirst ? "12px 0 0 0" : "16px 0 0 0"};
|
||||
${(props) =>
|
||||
@ -145,18 +268,23 @@ const StyledFilterBlockItemHeader = styled.div`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const StyledFilterBlockItemContent = styled.div`
|
||||
const StyledFilterBlockItemContent = styled.div<{
|
||||
withoutSeparator?: boolean;
|
||||
withMultiItems?: boolean;
|
||||
}>`
|
||||
margin: ${(props) =>
|
||||
props.withoutSeparator ? "12px -16px 0 0" : "12px -16px 16px 0"};
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin: ${(props) =>
|
||||
props.withoutSeparator ? "12px 0 0 -16px" : "12px 0 16px -16px"};
|
||||
margin: ${props.withoutSeparator
|
||||
? "12px 0 0 -16px"
|
||||
: "12px 0 16px -16px"};
|
||||
`
|
||||
: css`
|
||||
margin: ${(props) =>
|
||||
props.withoutSeparator ? "12px -16px 0 0" : "12px -16px 16px 0"};
|
||||
margin: ${props.withoutSeparator
|
||||
? "12px -16px 0 0"
|
||||
: "12px -16px 16px 0"};
|
||||
`}
|
||||
height: fit-content;
|
||||
|
||||
@ -195,19 +323,19 @@ const StyledFilterBlockItemSelectorText = styled(Text)`
|
||||
|
||||
StyledFilterBlockItemSelectorText.defaultProps = { theme: Base };
|
||||
|
||||
const selectedItemTag = css`
|
||||
background: ${(props) =>
|
||||
props.theme.filterInput.filter.selectedItem.background};
|
||||
border-color: ${(props) =>
|
||||
props.theme.filterInput.filter.selectedItem.border};
|
||||
`;
|
||||
// const selectedItemTag = css`
|
||||
// background: ${(props) =>
|
||||
// props.theme.filterInput.filter.selectedItem.background};
|
||||
// border-color: ${(props) =>
|
||||
// props.theme.filterInput.filter.selectedItem.border};
|
||||
// `;
|
||||
|
||||
const selectedItemTagText = css`
|
||||
color: ${(props) => props.theme.filterInput.filter.selectedItem.color};
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const StyledFilterBlockItemTagText = styled(Text)`
|
||||
const StyledFilterBlockItemTagText = styled(Text)<{ isSelected?: boolean }>`
|
||||
height: 20px;
|
||||
|
||||
font-weight: 400;
|
||||
@ -389,7 +517,164 @@ const StyledCrossIcon = styled(CrossIcon)`
|
||||
|
||||
StyledCrossIcon.defaultProps = { theme: Base };
|
||||
|
||||
const selectedViewIcon = css`
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.filterInput.sort.selectedViewIcon};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const notSelectedViewIcon = css`
|
||||
svg {
|
||||
path {
|
||||
fill: ${(props) => props.theme.filterInput.sort.viewIcon};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSortButton = styled.div<{ viewAs: TViewAs; isDesc: boolean }>`
|
||||
.combo-button {
|
||||
background: ${(props) =>
|
||||
props.theme.filterInput.sort.background} !important;
|
||||
|
||||
.icon-button_svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.sort-combo-box {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-right: 8px;
|
||||
`
|
||||
: css`
|
||||
margin-left: 8px;
|
||||
`}
|
||||
|
||||
.dropdown-container {
|
||||
top: 102%;
|
||||
bottom: auto;
|
||||
min-width: 200px;
|
||||
margin-top: 3px;
|
||||
|
||||
.view-selector-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
cursor: auto;
|
||||
|
||||
.view-selector {
|
||||
width: 44px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
cursor: auto;
|
||||
|
||||
.view-selector-icon {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.view-selector-icon:nth-child(1) {
|
||||
${(props) =>
|
||||
props.viewAs === "row" ? selectedViewIcon : notSelectedViewIcon};
|
||||
}
|
||||
|
||||
.view-selector-icon:nth-child(2) {
|
||||
${(props) =>
|
||||
props.viewAs !== "row" ? selectedViewIcon : notSelectedViewIcon};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.option-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-width: 200px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.option-item__icon {
|
||||
display: flex;
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
${(props) =>
|
||||
props.isDesc &&
|
||||
css`
|
||||
transform: rotate(180deg);
|
||||
`}
|
||||
|
||||
path {
|
||||
fill: ${(props) => props.theme.filterInput.sort.sortFill};
|
||||
}
|
||||
}
|
||||
|
||||
:hover {
|
||||
.option-item__icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-option-item {
|
||||
background: ${(props) => props.theme.filterInput.sort.hoverBackground};
|
||||
cursor: auto;
|
||||
|
||||
.selected-option-item__icon {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.optionalBlock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
${(props) =>
|
||||
props.theme.interfaceDirection === "rtl"
|
||||
? css`
|
||||
margin-left: 0;
|
||||
`
|
||||
: css`
|
||||
margin-right: 0;
|
||||
`}
|
||||
}
|
||||
|
||||
.combo-buttons_arrow-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.backdrop-active {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSortButton.defaultProps = { theme: Base };
|
||||
|
||||
export {
|
||||
StyledSortButton,
|
||||
StyledFilterBlock,
|
||||
StyledFilterBlockHeader,
|
||||
StyledFilterBlockItem,
|
||||
@ -408,3 +693,5 @@ export {
|
||||
StyledControlContainer,
|
||||
StyledCrossIcon,
|
||||
};
|
||||
|
||||
export { StyledFilterInput, StyledSearchInput, StyledButton };
|
205
packages/shared/components/filter/Filter.types.ts
Normal file
205
packages/shared/components/filter/Filter.types.ts
Normal file
@ -0,0 +1,205 @@
|
||||
import { DeviceType, FilterGroups } from "../../enums";
|
||||
import { TViewAs } from "../../types";
|
||||
|
||||
import { TViewSelectorOption } from "../view-selector";
|
||||
import { TOption } from "../combobox";
|
||||
|
||||
export type TSortDataItem = {
|
||||
id: string;
|
||||
className: string;
|
||||
key: string;
|
||||
isSelected: boolean;
|
||||
label: string;
|
||||
sortDirection: string;
|
||||
sortId: string;
|
||||
};
|
||||
|
||||
export type TGetSortData = () => TSortDataItem[];
|
||||
|
||||
export type TGetSelectedSortData = () => TSortDataItem;
|
||||
|
||||
export type TOnChangeViewAs = () => void;
|
||||
|
||||
export type TOnSort = (key: string, sortDirection: string) => void;
|
||||
|
||||
export type TOnSortButtonClick = (value: boolean) => void;
|
||||
|
||||
export interface SortButtonProps {
|
||||
id: string;
|
||||
getSortData: TGetSortData;
|
||||
getSelectedSortData: TGetSelectedSortData;
|
||||
|
||||
onChangeViewAs: TOnChangeViewAs;
|
||||
view: string;
|
||||
viewAs: TViewAs;
|
||||
viewSettings: TViewSelectorOption[];
|
||||
|
||||
onSort: TOnSort;
|
||||
viewSelectorVisible: boolean;
|
||||
|
||||
onSortButtonClick: TOnSortButtonClick;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type TChangeFilterValue = (
|
||||
group: FilterGroups,
|
||||
key: string | string[],
|
||||
isSelected: boolean,
|
||||
label?: string,
|
||||
isMultiSelect?: boolean,
|
||||
withOptions?: boolean,
|
||||
) => void;
|
||||
|
||||
export type TShowSelector = (selectorType: string, group: FilterGroups) => void;
|
||||
|
||||
export type TSelectorItem = {
|
||||
group: FilterGroups;
|
||||
isSelected?: boolean;
|
||||
selectedKey?: string;
|
||||
displaySelectorType?: string;
|
||||
key: string;
|
||||
selectedLabel?: string;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export type TToggleButtonItem = {
|
||||
key: string;
|
||||
label?: string;
|
||||
isSelected?: boolean;
|
||||
isToggle?: boolean;
|
||||
};
|
||||
|
||||
export type TWithOptionItem = {
|
||||
options: TOption[];
|
||||
withOptions?: boolean;
|
||||
id?: string;
|
||||
key?: string;
|
||||
isSelected?: boolean;
|
||||
};
|
||||
|
||||
export type TCheckboxItem = {
|
||||
key: string;
|
||||
id: string;
|
||||
label?: string;
|
||||
isSelected?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isCheckbox?: boolean;
|
||||
};
|
||||
|
||||
export type TTagItem = {
|
||||
group: FilterGroups;
|
||||
key: string | string[];
|
||||
label?: string;
|
||||
isSelected?: boolean;
|
||||
id?: string;
|
||||
isMultiSelect?: boolean;
|
||||
};
|
||||
|
||||
export type TGroupItem =
|
||||
| TTagItem
|
||||
| TCheckboxItem
|
||||
| TWithOptionItem
|
||||
| TSelectorItem
|
||||
| TToggleButtonItem;
|
||||
|
||||
export interface FilterBlockItemProps {
|
||||
group: FilterGroups;
|
||||
label: string;
|
||||
groupItem: TGroupItem[];
|
||||
isLast: boolean;
|
||||
withoutHeader: boolean;
|
||||
withoutSeparator: boolean;
|
||||
changeFilterValue: TChangeFilterValue;
|
||||
showSelector: TShowSelector;
|
||||
isFirst: boolean;
|
||||
withMultiItems: boolean;
|
||||
}
|
||||
|
||||
export type TItem = {
|
||||
id?: string;
|
||||
key: string | string[];
|
||||
label: string;
|
||||
group: FilterGroups;
|
||||
isLast?: boolean;
|
||||
withoutHeader?: boolean;
|
||||
withoutSeparator?: boolean;
|
||||
withMultiItems?: boolean;
|
||||
isHeader?: boolean;
|
||||
isSelected?: boolean;
|
||||
groupItem?: TGroupItem[];
|
||||
selectedKey?: string;
|
||||
displaySelectorType?: string;
|
||||
isMultiSelect?: boolean;
|
||||
selectedLabel?: string;
|
||||
};
|
||||
|
||||
export type TGetFilterData = () => Promise<TItem[]>;
|
||||
export type TOnFilter = (value: TItem[] | TGroupItem[]) => void;
|
||||
|
||||
export interface FilterBlockProps {
|
||||
selectedFilterValue: TItem[];
|
||||
filterHeader: string;
|
||||
getFilterData: TGetFilterData;
|
||||
hideFilterBlock: () => void;
|
||||
onFilter: TOnFilter;
|
||||
selectorLabel: string;
|
||||
userId: string;
|
||||
isRooms: boolean;
|
||||
isAccounts: boolean;
|
||||
}
|
||||
|
||||
export interface FilterButtonProps {
|
||||
onFilter: TOnFilter;
|
||||
getFilterData: TGetFilterData;
|
||||
selectedFilterValue: TItem[];
|
||||
filterHeader: string;
|
||||
selectorLabel: string;
|
||||
isRooms: boolean;
|
||||
isAccounts: boolean;
|
||||
id: string;
|
||||
title: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface FilterProps {
|
||||
onFilter: TOnFilter;
|
||||
getFilterData: TGetFilterData;
|
||||
getSelectedFilterData: () => Promise<TItem[]>;
|
||||
onSort: TOnSort;
|
||||
getSortData: TGetSortData;
|
||||
getSelectedSortData: TGetSelectedSortData;
|
||||
view: string;
|
||||
viewAs: TViewAs;
|
||||
viewSelectorVisible: boolean;
|
||||
getViewSettingsData: () => TViewSelectorOption[];
|
||||
onChangeViewAs: TOnChangeViewAs;
|
||||
placeholder: string;
|
||||
onSearch: (value: string) => void;
|
||||
getSelectedInputValue: () => string;
|
||||
|
||||
filterHeader: string;
|
||||
selectorLabel: string;
|
||||
clearAll: () => void;
|
||||
|
||||
isRecentFolder: boolean;
|
||||
removeSelectedItem: ({
|
||||
key,
|
||||
group,
|
||||
}: {
|
||||
key: string;
|
||||
group?: FilterGroups;
|
||||
}) => void;
|
||||
|
||||
isRooms: boolean;
|
||||
isAccounts: boolean;
|
||||
filterTitle: string;
|
||||
sortByTitle: string;
|
||||
|
||||
clearSearch: boolean;
|
||||
setClearSearch: (value: boolean) => void;
|
||||
|
||||
onSortButtonClick: TOnSortButtonClick;
|
||||
onClearFilter: () => void;
|
||||
currentDeviceType: DeviceType;
|
||||
userId: string;
|
||||
}
|
@ -1,18 +1,22 @@
|
||||
import React from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ViewSelector } from "@docspace/shared/components/view-selector";
|
||||
import { Link } from "@docspace/shared/components/link";
|
||||
import { DeviceType, FilterGroups } from "../../enums";
|
||||
|
||||
import { TViewSelectorOption, ViewSelector } from "../view-selector";
|
||||
import { Link, LinkType } from "../link";
|
||||
import { SelectedItem } from "../selected-item";
|
||||
import { InputSize } from "../text-input";
|
||||
|
||||
import FilterButton from "./sub-components/FilterButton";
|
||||
import SortButton from "./sub-components/SortButton";
|
||||
import { SelectedItem } from "@docspace/shared/components/selected-item";
|
||||
import { useTheme } from "styled-components";
|
||||
import { StyledFilterInput, StyledSearchInput } from "./StyledFilterInput";
|
||||
import { DeviceType } from "@docspace/shared/enums";
|
||||
|
||||
import { StyledFilterInput, StyledSearchInput } from "./Filter.styled";
|
||||
import { FilterProps, TItem } from "./Filter.types";
|
||||
|
||||
const FilterInput = React.memo(
|
||||
({
|
||||
t,
|
||||
onFilter,
|
||||
getFilterData,
|
||||
getSelectedFilterData,
|
||||
@ -35,7 +39,6 @@ const FilterInput = React.memo(
|
||||
isRecentFolder,
|
||||
removeSelectedItem,
|
||||
|
||||
isPersonalRoom,
|
||||
isRooms,
|
||||
isAccounts,
|
||||
filterTitle,
|
||||
@ -47,11 +50,18 @@ const FilterInput = React.memo(
|
||||
onSortButtonClick,
|
||||
onClearFilter,
|
||||
currentDeviceType,
|
||||
}) => {
|
||||
const [viewSettings, setViewSettings] = React.useState([]);
|
||||
userId,
|
||||
}: FilterProps) => {
|
||||
const [viewSettings, setViewSettings] = React.useState<
|
||||
TViewSelectorOption[]
|
||||
>([]);
|
||||
const [inputValue, setInputValue] = React.useState("");
|
||||
const [selectedFilterValue, setSelectedFilterValue] = React.useState(null);
|
||||
const [selectedItems, setSelectedItems] = React.useState(null);
|
||||
const [selectedFilterValue, setSelectedFilterValue] = React.useState<
|
||||
TItem[]
|
||||
>([]);
|
||||
const [selectedItems, setSelectedItems] = React.useState<TItem[]>([]);
|
||||
|
||||
const { t } = useTranslation(["Common"]);
|
||||
|
||||
const mountRef = React.useRef(true);
|
||||
const { interfaceDirection } = useTheme();
|
||||
@ -60,7 +70,7 @@ const FilterInput = React.memo(
|
||||
? { marginRight: "8px" }
|
||||
: { marginLeft: "8px" };
|
||||
React.useEffect(() => {
|
||||
const value = getViewSettingsData && getViewSettingsData();
|
||||
const value = getViewSettingsData?.();
|
||||
|
||||
if (value) setViewSettings(value);
|
||||
}, [getViewSettingsData]);
|
||||
@ -68,35 +78,37 @@ const FilterInput = React.memo(
|
||||
React.useEffect(() => {
|
||||
if (clearSearch) {
|
||||
setInputValue("");
|
||||
onClearFilter && onClearFilter();
|
||||
onClearFilter?.();
|
||||
setClearSearch(false);
|
||||
}
|
||||
}, [clearSearch]);
|
||||
}, [clearSearch, onClearFilter, setClearSearch]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const value = getSelectedInputValue && getSelectedInputValue();
|
||||
const value = getSelectedInputValue?.();
|
||||
|
||||
setInputValue(value);
|
||||
}, [getSelectedInputValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
getSelectedFilterDataAction();
|
||||
}, [getSelectedFilterDataAction, getSelectedFilterData]);
|
||||
|
||||
const getSelectedFilterDataAction = React.useCallback(async () => {
|
||||
const value = await getSelectedFilterData();
|
||||
|
||||
if (!mountRef.current) return;
|
||||
setSelectedFilterValue(value);
|
||||
|
||||
const newSelectedItems = [];
|
||||
const newSelectedItems: TItem[] = [];
|
||||
|
||||
value.forEach((item) => {
|
||||
if (item.isMultiSelect) {
|
||||
const newKeys = item.key.map((oldKey) => ({
|
||||
key: oldKey.key ? oldKey.key : oldKey,
|
||||
if (item.isMultiSelect && Array.isArray(item.key)) {
|
||||
const newKeys = item.key.map((oldKey: string | {}) => ({
|
||||
key:
|
||||
typeof oldKey !== "string" && "key" in oldKey && oldKey.key
|
||||
? (oldKey.key as string)
|
||||
: (oldKey as string),
|
||||
group: item.group,
|
||||
label: oldKey.label ? oldKey.label : oldKey,
|
||||
label:
|
||||
typeof oldKey !== "string" && "label" in oldKey && oldKey.label
|
||||
? (oldKey.label as string)
|
||||
: (oldKey as string),
|
||||
}));
|
||||
|
||||
return newSelectedItems.push(...newKeys);
|
||||
@ -108,21 +120,31 @@ const FilterInput = React.memo(
|
||||
setSelectedItems(newSelectedItems);
|
||||
}, [getSelectedFilterData]);
|
||||
|
||||
React.useEffect(() => {
|
||||
getSelectedFilterDataAction();
|
||||
}, [getSelectedFilterDataAction, getSelectedFilterData]);
|
||||
|
||||
const onClearSearch = React.useCallback(() => {
|
||||
onSearch && onSearch();
|
||||
onSearch?.("");
|
||||
}, [onSearch]);
|
||||
|
||||
const removeSelectedItemAction = React.useCallback(
|
||||
(key, label, group) => {
|
||||
(
|
||||
key: string,
|
||||
label: string | React.ReactNode,
|
||||
group?: string | FilterGroups,
|
||||
) => {
|
||||
const newItems = selectedItems
|
||||
.map((item) => ({ ...item }))
|
||||
.filter((item) => item.key != key);
|
||||
.filter((item) => item.key !== key);
|
||||
|
||||
setSelectedItems(newItems);
|
||||
|
||||
removeSelectedItem({ key, group });
|
||||
const newGroup = group as FilterGroups;
|
||||
|
||||
removeSelectedItem({ key, group: newGroup });
|
||||
},
|
||||
[selectedItems, removeSelectedItem]
|
||||
[selectedItems, removeSelectedItem],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -140,24 +162,22 @@ const FilterInput = React.memo(
|
||||
onChange={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
id="filter_search-input"
|
||||
size={InputSize.base}
|
||||
/>
|
||||
<FilterButton
|
||||
t={t}
|
||||
id="filter-button"
|
||||
onFilter={onFilter}
|
||||
getFilterData={getFilterData}
|
||||
selectedFilterValue={selectedFilterValue}
|
||||
filterHeader={filterHeader}
|
||||
selectorLabel={selectorLabel}
|
||||
isPersonalRoom={isPersonalRoom}
|
||||
isRooms={isRooms}
|
||||
isAccounts={isAccounts}
|
||||
title={filterTitle}
|
||||
currentDeviceType={currentDeviceType}
|
||||
userId={userId}
|
||||
/>
|
||||
{!isRecentFolder && (
|
||||
<SortButton
|
||||
t={t}
|
||||
id="sort-by-button"
|
||||
onSort={onSort}
|
||||
getSortData={getSortData}
|
||||
@ -186,7 +206,7 @@ const FilterInput = React.memo(
|
||||
viewAs={viewAs === "table" ? "row" : viewAs}
|
||||
viewSettings={viewSettings}
|
||||
onChangeView={onChangeViewAs}
|
||||
isFilter={true}
|
||||
isFilter
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@ -195,7 +215,7 @@ const FilterInput = React.memo(
|
||||
{selectedItems.map((item) => (
|
||||
<SelectedItem
|
||||
key={`${item.key}_${item.group}`}
|
||||
propKey={item.key}
|
||||
propKey={Array.isArray(item.key) ? item.key[0] : item.key}
|
||||
label={item.selectedLabel ? item.selectedLabel : item.label}
|
||||
group={item.group}
|
||||
onClose={removeSelectedItemAction}
|
||||
@ -204,11 +224,11 @@ const FilterInput = React.memo(
|
||||
))}
|
||||
{selectedItems.filter((item) => item.label).length > 1 && (
|
||||
<Link
|
||||
className={"clear-all-link"}
|
||||
className="clear-all-link"
|
||||
isHovered
|
||||
fontWeight={600}
|
||||
isSemitransparent
|
||||
type="action"
|
||||
type={LinkType.action}
|
||||
onClick={clearAll}
|
||||
>
|
||||
{t("Common:ClearAll")}
|
||||
@ -218,11 +238,9 @@ const FilterInput = React.memo(
|
||||
)}
|
||||
</StyledFilterInput>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
FilterInput.defaultProps = {
|
||||
viewSelectorVisible: false,
|
||||
};
|
||||
FilterInput.displayName = "FilterInput";
|
||||
|
||||
export default FilterInput;
|
555
packages/shared/components/filter/sub-components/FilterBlock.tsx
Normal file
555
packages/shared/components/filter/sub-components/FilterBlock.tsx
Normal file
@ -0,0 +1,555 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import ClearReactSvgUrl from "PUBLIC_DIR/images/clear.react.svg?url";
|
||||
|
||||
import PeopleSelector from "../../../selectors/People";
|
||||
import RoomSelector from "../../../selectors/Room";
|
||||
import { FilterGroups, FilterSelectorTypes } from "../../../enums";
|
||||
import { FilterBlockLoader } from "../../../skeletons/filter";
|
||||
|
||||
import { Backdrop } from "../../backdrop";
|
||||
import { Button, ButtonSize } from "../../button";
|
||||
import { Heading, HeadingSize } from "../../heading";
|
||||
import { IconButton } from "../../icon-button";
|
||||
import { Scrollbar } from "../../scrollbar";
|
||||
import { Portal } from "../../portal";
|
||||
import { TSelectorItem } from "../../selector";
|
||||
|
||||
import {
|
||||
StyledFilterBlock,
|
||||
StyledFilterBlockHeader,
|
||||
StyledFilterBlockFooter,
|
||||
StyledControlContainer,
|
||||
StyledCrossIcon,
|
||||
} from "../Filter.styled";
|
||||
|
||||
import { FilterBlockProps, TGroupItem, TItem } from "../Filter.types";
|
||||
|
||||
import FilterBlockItem from "./FilterBlockItem";
|
||||
|
||||
const FilterBlock = ({
|
||||
selectedFilterValue,
|
||||
filterHeader,
|
||||
getFilterData,
|
||||
hideFilterBlock,
|
||||
onFilter,
|
||||
selectorLabel,
|
||||
userId,
|
||||
isRooms,
|
||||
isAccounts,
|
||||
}: FilterBlockProps) => {
|
||||
const { t } = useTranslation(["Common"]);
|
||||
|
||||
const [showSelector, setShowSelector] = React.useState<{
|
||||
show: boolean;
|
||||
type: string | null;
|
||||
group: FilterGroups | null;
|
||||
}>({
|
||||
show: false,
|
||||
type: null,
|
||||
group: null,
|
||||
});
|
||||
|
||||
const [filterData, setFilterData] = React.useState<TItem[]>([]);
|
||||
const [filterValues, setFilterValues] = React.useState<TGroupItem[]>([]);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
|
||||
const setFilterDataFn = (data: TItem[]) => {
|
||||
const filterSubject = data.find(
|
||||
(f) => f.group === FilterGroups.roomFilterSubject,
|
||||
);
|
||||
|
||||
if (filterSubject && filterSubject.groupItem) {
|
||||
const filterOwner = data.find(
|
||||
(f) => f.group === FilterGroups.roomFilterOwner,
|
||||
);
|
||||
|
||||
const isSelected =
|
||||
filterSubject.groupItem.findIndex((i) => i.isSelected) > -1;
|
||||
|
||||
if (
|
||||
filterOwner &&
|
||||
filterOwner.groupItem &&
|
||||
"isDisabled" in filterOwner.groupItem[0]
|
||||
)
|
||||
filterOwner.groupItem[0].isDisabled = !isSelected;
|
||||
}
|
||||
|
||||
setFilterData(data);
|
||||
};
|
||||
|
||||
const changeShowSelector = React.useCallback(
|
||||
(selectorType: string, group: FilterGroups) => {
|
||||
setShowSelector((val) => ({
|
||||
show: !val.show,
|
||||
type: selectorType,
|
||||
group,
|
||||
}));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const changeSelectedItems = React.useCallback(
|
||||
(filter: TGroupItem[]) => {
|
||||
const data = filterData.map((item) => ({ ...item }));
|
||||
|
||||
data.forEach((item) => {
|
||||
if (
|
||||
filter.find((value) => "group" in value && value.group === item.group)
|
||||
) {
|
||||
const currentFilter = filter.find(
|
||||
(value) => "group" in value && value.group === item.group,
|
||||
);
|
||||
|
||||
item.groupItem?.forEach((groupItem) => {
|
||||
groupItem.isSelected = false;
|
||||
if (currentFilter && groupItem.key === currentFilter.key) {
|
||||
groupItem.isSelected = true;
|
||||
}
|
||||
if (
|
||||
"displaySelectorType" in groupItem &&
|
||||
groupItem.displaySelectorType &&
|
||||
currentFilter &&
|
||||
!Array.isArray(currentFilter.key) &&
|
||||
"label" in currentFilter
|
||||
) {
|
||||
groupItem.isSelected = true;
|
||||
groupItem.selectedKey = currentFilter.key;
|
||||
groupItem.selectedLabel = currentFilter.label;
|
||||
}
|
||||
if (
|
||||
"isMultiSelect" in groupItem &&
|
||||
groupItem.isMultiSelect &&
|
||||
currentFilter &&
|
||||
Array.isArray(currentFilter.key) &&
|
||||
typeof groupItem.key === "string"
|
||||
) {
|
||||
groupItem.isSelected = currentFilter.key.includes(groupItem.key);
|
||||
}
|
||||
if (
|
||||
"withOptions" in groupItem &&
|
||||
groupItem.withOptions &&
|
||||
currentFilter &&
|
||||
Array.isArray(currentFilter.key) &&
|
||||
typeof groupItem.key === "string"
|
||||
) {
|
||||
groupItem.isSelected = currentFilter.key.includes(groupItem.key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
item.groupItem?.forEach((groupItem) => {
|
||||
groupItem.isSelected = false;
|
||||
if (
|
||||
"displaySelectorType" in groupItem &&
|
||||
groupItem.displaySelectorType
|
||||
) {
|
||||
groupItem.selectedKey = undefined;
|
||||
groupItem.selectedLabel = undefined;
|
||||
}
|
||||
if (
|
||||
"withOptions" in groupItem &&
|
||||
groupItem.withOptions &&
|
||||
"options" in groupItem
|
||||
) {
|
||||
groupItem.options.forEach((x: unknown, index: number) => {
|
||||
groupItem.options[index].isSelected = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setFilterDataFn(data);
|
||||
},
|
||||
[filterData],
|
||||
);
|
||||
|
||||
const onClearFilter = React.useCallback(() => {
|
||||
changeSelectedItems([]);
|
||||
setFilterValues([]);
|
||||
|
||||
if (selectedFilterValue && selectedFilterValue.length > 0 && onFilter)
|
||||
onFilter([]);
|
||||
}, [changeSelectedItems, onFilter, selectedFilterValue]);
|
||||
|
||||
const changeFilterValue = React.useCallback(
|
||||
(
|
||||
group: FilterGroups,
|
||||
key: string | string[],
|
||||
isSelected: boolean,
|
||||
label?: string,
|
||||
isMultiSelect?: boolean,
|
||||
) => {
|
||||
let value = filterValues.map((v: TGroupItem) => {
|
||||
if (Array.isArray(v.key)) {
|
||||
const newKey = [...v.key];
|
||||
v.key = newKey;
|
||||
}
|
||||
|
||||
return {
|
||||
...v,
|
||||
};
|
||||
});
|
||||
|
||||
if (isSelected) {
|
||||
if (isMultiSelect) {
|
||||
const groupIdx = value.findIndex(
|
||||
(item) => "group" in item && item.group === group,
|
||||
);
|
||||
|
||||
const groupItemKey = value[groupIdx].key;
|
||||
|
||||
if (Array.isArray(groupItemKey)) {
|
||||
const itemIdx = groupItemKey.findIndex((item) => item === key);
|
||||
|
||||
groupItemKey.splice(itemIdx, 1);
|
||||
|
||||
if (groupItemKey.length === 0)
|
||||
value = value.filter(
|
||||
(item) => "group" in item && item.group !== group,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
value = value.filter(
|
||||
(item) => "group" in item && item.group !== group,
|
||||
);
|
||||
}
|
||||
|
||||
setFilterValues(value);
|
||||
changeSelectedItems(value);
|
||||
|
||||
const idx = selectedFilterValue.findIndex(
|
||||
(item) => item.group === group,
|
||||
);
|
||||
|
||||
if (idx > -1) {
|
||||
if (isMultiSelect) {
|
||||
const groupItemKey = selectedFilterValue[idx].key;
|
||||
|
||||
if (Array.isArray(groupItemKey)) {
|
||||
const itemIdx = groupItemKey.findIndex((item) => item === key);
|
||||
|
||||
if (itemIdx === -1) return;
|
||||
|
||||
groupItemKey.splice(itemIdx, 1);
|
||||
|
||||
return onFilter(selectedFilterValue);
|
||||
}
|
||||
|
||||
onFilter(value);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.find((item) => "group" in item && item.group === group)) {
|
||||
value.forEach((item) => {
|
||||
if ("group" in item && item.group === group) {
|
||||
if (
|
||||
isMultiSelect &&
|
||||
Array.isArray(item.key) &&
|
||||
!Array.isArray(key)
|
||||
) {
|
||||
item.key.push(key);
|
||||
} else {
|
||||
item.key = key;
|
||||
if (label) {
|
||||
item.label = label;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (label) {
|
||||
value.push({ group, key, label });
|
||||
} else if (isMultiSelect && typeof key === "string") {
|
||||
value.push({ group, key: [key] });
|
||||
} else {
|
||||
value.push({ group, key });
|
||||
}
|
||||
|
||||
setFilterValues(value);
|
||||
changeSelectedItems(value);
|
||||
},
|
||||
[filterValues, changeSelectedItems, selectedFilterValue, onFilter],
|
||||
);
|
||||
|
||||
const getDefaultFilterData = React.useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
const data = await getFilterData();
|
||||
|
||||
const items = data.filter((item) => item.isHeader === true);
|
||||
|
||||
items.forEach((item) => {
|
||||
const groupItem = data.filter(
|
||||
(val) => val.group === item.group && val.isHeader !== true,
|
||||
);
|
||||
|
||||
groupItem.forEach((i) => (i.isSelected = false));
|
||||
|
||||
item.groupItem = groupItem as TGroupItem[];
|
||||
});
|
||||
|
||||
if (selectedFilterValue) {
|
||||
selectedFilterValue.forEach((selectedValue) => {
|
||||
items.forEach((item) => {
|
||||
if (item.group === selectedValue.group) {
|
||||
item.groupItem?.forEach((groupItem) => {
|
||||
if (
|
||||
groupItem.key === selectedValue.key ||
|
||||
("displaySelectorType" in groupItem &&
|
||||
groupItem.displaySelectorType)
|
||||
) {
|
||||
groupItem.isSelected = true;
|
||||
if (
|
||||
"displaySelectorType" in groupItem &&
|
||||
groupItem.displaySelectorType
|
||||
) {
|
||||
groupItem.selectedLabel = selectedValue.label;
|
||||
groupItem.selectedKey = Array.isArray(selectedValue.key)
|
||||
? ""
|
||||
: selectedValue.key;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
"isMultiSelect" in groupItem &&
|
||||
groupItem.isMultiSelect &&
|
||||
Array.isArray(selectedValue.key) &&
|
||||
typeof groupItem.key === "string"
|
||||
) {
|
||||
groupItem.isSelected = selectedValue.key.includes(
|
||||
groupItem.key,
|
||||
);
|
||||
}
|
||||
|
||||
if ("withOptions" in groupItem && groupItem.withOptions) {
|
||||
groupItem.options.forEach(
|
||||
(option) =>
|
||||
(option.isSelected = option.key === selectedValue.key),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const newFilterValues = selectedFilterValue.map((value) => {
|
||||
if (Array.isArray(value.key)) {
|
||||
const newKey = [...value.key];
|
||||
value.key = newKey;
|
||||
}
|
||||
|
||||
return {
|
||||
...value,
|
||||
};
|
||||
});
|
||||
|
||||
setFilterDataFn(items);
|
||||
setFilterValues(newFilterValues as TGroupItem[]);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 500);
|
||||
}, [getFilterData, selectedFilterValue]);
|
||||
|
||||
React.useEffect(() => {
|
||||
getDefaultFilterData();
|
||||
}, [getDefaultFilterData]);
|
||||
|
||||
const onFilterAction = React.useCallback(() => {
|
||||
onFilter?.(filterValues);
|
||||
hideFilterBlock();
|
||||
}, [onFilter, hideFilterBlock, filterValues]);
|
||||
|
||||
const onArrowClick = React.useCallback(() => {
|
||||
setShowSelector((val) => ({ ...val, show: false }));
|
||||
}, []);
|
||||
|
||||
const selectOption = React.useCallback(
|
||||
(items: TSelectorItem[]) => {
|
||||
setShowSelector((val) => ({
|
||||
...val,
|
||||
show: false,
|
||||
}));
|
||||
|
||||
if (showSelector.group)
|
||||
changeFilterValue(
|
||||
showSelector.group,
|
||||
`${items[0].id}`,
|
||||
false,
|
||||
items[0].label || "",
|
||||
);
|
||||
},
|
||||
[showSelector.group, changeFilterValue],
|
||||
);
|
||||
|
||||
const isEqualFilter = () => {
|
||||
let isEqual = true;
|
||||
|
||||
if (
|
||||
filterValues.length === 0 ||
|
||||
selectedFilterValue.length > filterValues.length
|
||||
)
|
||||
return !isEqual;
|
||||
|
||||
if (
|
||||
(selectedFilterValue.length === 0 && filterValues.length > 0) ||
|
||||
selectedFilterValue.length !== filterValues.length
|
||||
) {
|
||||
isEqual = false;
|
||||
|
||||
return !isEqual;
|
||||
}
|
||||
|
||||
filterValues.forEach((value) => {
|
||||
const oldValue = selectedFilterValue.find(
|
||||
(item) =>
|
||||
"group" in item && "group" in value && item.group === value.group,
|
||||
);
|
||||
|
||||
let isMultiSelectEqual = false;
|
||||
let withOptionsEqual = false;
|
||||
|
||||
if (Array.isArray(value.key) && oldValue) {
|
||||
isMultiSelectEqual = true;
|
||||
value.key.forEach(
|
||||
(item) =>
|
||||
(isMultiSelectEqual =
|
||||
isMultiSelectEqual && oldValue.key.includes(item)),
|
||||
);
|
||||
}
|
||||
|
||||
if (oldValue && "options" in value && value.options) {
|
||||
withOptionsEqual = true;
|
||||
value.options.forEach(
|
||||
(option) =>
|
||||
(withOptionsEqual =
|
||||
isMultiSelectEqual && option.key === oldValue.key),
|
||||
);
|
||||
}
|
||||
|
||||
isEqual =
|
||||
isEqual &&
|
||||
(oldValue?.key === value.key || isMultiSelectEqual || withOptionsEqual);
|
||||
});
|
||||
|
||||
return !isEqual;
|
||||
};
|
||||
|
||||
const showFooter = isEqualFilter();
|
||||
|
||||
const filterBlockComponent = (
|
||||
<>
|
||||
{showSelector.show ? (
|
||||
<StyledFilterBlock>
|
||||
{showSelector.type === FilterSelectorTypes.people ? (
|
||||
<PeopleSelector
|
||||
withOutCurrentAuthorizedUser
|
||||
className="people-selector"
|
||||
isMultiSelect={false}
|
||||
onAccept={selectOption}
|
||||
onBackClick={onArrowClick}
|
||||
headerLabel={selectorLabel}
|
||||
currentUserId={userId}
|
||||
/>
|
||||
) : (
|
||||
<RoomSelector
|
||||
className="people-selector"
|
||||
isMultiSelect={false}
|
||||
onAccept={selectOption}
|
||||
onBackClick={onArrowClick}
|
||||
headerLabel={selectorLabel}
|
||||
/>
|
||||
)}
|
||||
<StyledControlContainer onClick={hideFilterBlock}>
|
||||
<StyledCrossIcon />
|
||||
</StyledControlContainer>
|
||||
</StyledFilterBlock>
|
||||
) : (
|
||||
<StyledFilterBlock showFooter={showFooter}>
|
||||
<StyledFilterBlockHeader>
|
||||
<Heading size={HeadingSize.medium}>{filterHeader}</Heading>
|
||||
{showFooter && (
|
||||
<IconButton
|
||||
id="filter_search-options-clear"
|
||||
iconName={ClearReactSvgUrl}
|
||||
isFill
|
||||
onClick={onClearFilter}
|
||||
size={17}
|
||||
/>
|
||||
)}
|
||||
</StyledFilterBlockHeader>
|
||||
<div className="filter-body">
|
||||
{isLoading ? (
|
||||
<FilterBlockLoader isRooms={isRooms} isAccounts={isAccounts} />
|
||||
) : (
|
||||
<Scrollbar className="filter-body__scrollbar">
|
||||
{filterData.map((item: TItem, index) => {
|
||||
return (
|
||||
<FilterBlockItem
|
||||
key={typeof item.key === "string" ? item.key : ""}
|
||||
label={item.label}
|
||||
group={item.group}
|
||||
groupItem={item.groupItem || []}
|
||||
isLast={item.isLast || false}
|
||||
isFirst={index === 0}
|
||||
withoutHeader={item.withoutHeader || false}
|
||||
withoutSeparator={item.withoutSeparator || false}
|
||||
changeFilterValue={changeFilterValue}
|
||||
showSelector={changeShowSelector}
|
||||
withMultiItems={item.withMultiItems || false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Scrollbar>
|
||||
)}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<StyledFilterBlockFooter>
|
||||
<Button
|
||||
id="filter_apply-button"
|
||||
size={ButtonSize.normal}
|
||||
primary
|
||||
label={t("Common:ApplyButton")}
|
||||
scale
|
||||
onClick={onFilterAction}
|
||||
/>
|
||||
<Button
|
||||
id="filter_cancel-button"
|
||||
size={ButtonSize.normal}
|
||||
label={t("Common:CancelButton")}
|
||||
scale
|
||||
onClick={hideFilterBlock}
|
||||
/>
|
||||
</StyledFilterBlockFooter>
|
||||
)}
|
||||
|
||||
<StyledControlContainer id="filter_close" onClick={hideFilterBlock}>
|
||||
<StyledCrossIcon />
|
||||
</StyledControlContainer>
|
||||
</StyledFilterBlock>
|
||||
)}
|
||||
|
||||
<Backdrop visible withBackground onClick={hideFilterBlock} zIndex={215} />
|
||||
</>
|
||||
);
|
||||
|
||||
const renderPortalFilterBlock = () => {
|
||||
const rootElement = document.getElementById("root");
|
||||
|
||||
return (
|
||||
<Portal
|
||||
element={filterBlockComponent}
|
||||
appendTo={rootElement || undefined}
|
||||
visible
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return renderPortalFilterBlock();
|
||||
};
|
||||
|
||||
export default React.memo(FilterBlock);
|
@ -1,9 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import { SelectorAddButton } from "@docspace/shared/components/selector-add-button";
|
||||
import { Heading } from "@docspace/shared/components/heading";
|
||||
import { ComboBox } from "@docspace/shared/components/combobox";
|
||||
import { Checkbox } from "@docspace/shared/components/checkbox";
|
||||
import XIcon from "PUBLIC_DIR/images/x.react.svg";
|
||||
|
||||
import { FilterGroups, FilterKeys, FilterSelectorTypes } from "../../../enums";
|
||||
|
||||
import { SelectorAddButton } from "../../selector-add-button";
|
||||
import { Heading, HeadingSize } from "../../heading";
|
||||
import { ComboBox } from "../../combobox";
|
||||
import { Checkbox } from "../../checkbox";
|
||||
import { ColorTheme, ThemeId } from "../../color-theme";
|
||||
|
||||
import {
|
||||
StyledFilterBlockItem,
|
||||
@ -18,16 +23,16 @@ import {
|
||||
StyledFilterBlockItemToggleButton,
|
||||
StyledFilterBlockItemCheckboxContainer,
|
||||
StyledFilterBlockItemSeparator,
|
||||
} from "./StyledFilterBlock";
|
||||
|
||||
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
|
||||
|
||||
import XIcon from "PUBLIC_DIR/images/x.react.svg";
|
||||
} from "../Filter.styled";
|
||||
import {
|
||||
FilterGroups,
|
||||
FilterKeys,
|
||||
FilterSelectorTypes,
|
||||
} from "@docspace/shared/enums";
|
||||
FilterBlockItemProps,
|
||||
TCheckboxItem,
|
||||
TGroupItem,
|
||||
TSelectorItem,
|
||||
TTagItem,
|
||||
TToggleButtonItem,
|
||||
TWithOptionItem,
|
||||
} from "../Filter.types";
|
||||
|
||||
const FilterBlockItem = ({
|
||||
group,
|
||||
@ -40,42 +45,46 @@ const FilterBlockItem = ({
|
||||
showSelector,
|
||||
isFirst,
|
||||
withMultiItems,
|
||||
}) => {
|
||||
}: FilterBlockItemProps) => {
|
||||
const changeFilterValueAction = (
|
||||
key,
|
||||
isSelected,
|
||||
isMultiSelect,
|
||||
withOptions
|
||||
key: string | string[],
|
||||
isSelected?: boolean,
|
||||
isMultiSelect?: boolean,
|
||||
withOptions?: boolean,
|
||||
) => {
|
||||
changeFilterValue &&
|
||||
changeFilterValue(
|
||||
group,
|
||||
key,
|
||||
isSelected,
|
||||
null,
|
||||
isMultiSelect,
|
||||
withOptions
|
||||
);
|
||||
changeFilterValue?.(
|
||||
group,
|
||||
key,
|
||||
isSelected || false,
|
||||
undefined,
|
||||
isMultiSelect || false,
|
||||
withOptions || false,
|
||||
);
|
||||
};
|
||||
|
||||
const showSelectorAction = (event, selectorType, group, ref) => {
|
||||
let target = event.target;
|
||||
const showSelectorAction = (
|
||||
event: React.MouseEvent,
|
||||
selectorType: string,
|
||||
g: FilterGroups,
|
||||
ref: HTMLDivElement | [],
|
||||
) => {
|
||||
let target = event.target as HTMLDivElement;
|
||||
|
||||
while (!!target.parentNode) {
|
||||
target = target.parentNode;
|
||||
while (target.parentNode) {
|
||||
target = target.parentNode as HTMLDivElement;
|
||||
|
||||
if (target === ref) {
|
||||
changeFilterValue && changeFilterValue(group, [], true);
|
||||
changeFilterValue?.(g, [], true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showSelector && showSelector(selectorType, group);
|
||||
showSelector?.(selectorType, g);
|
||||
};
|
||||
|
||||
const getSelectorItem = (item) => {
|
||||
const clearSelectorRef = React.useRef(null);
|
||||
const clearSelectorRef = React.useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const getSelectorItem = (item: TSelectorItem) => {
|
||||
const isRoomsSelector = item.group === FilterGroups.filterRoom;
|
||||
const selectorType = isRoomsSelector
|
||||
? FilterSelectorTypes.rooms
|
||||
@ -98,28 +107,27 @@ const FilterBlockItem = ({
|
||||
{item?.displaySelectorType === "button" && (
|
||||
<SelectorAddButton id="filter_add-author" />
|
||||
)}
|
||||
<StyledFilterBlockItemSelectorText noSelect={true}>
|
||||
<StyledFilterBlockItemSelectorText noSelect>
|
||||
{item.label}
|
||||
</StyledFilterBlockItemSelectorText>
|
||||
</StyledFilterBlockItemSelector>
|
||||
) : (
|
||||
<ColorTheme
|
||||
key={item.key}
|
||||
id={item.id}
|
||||
isSelected={item.isSelected}
|
||||
onClick={(event) =>
|
||||
onClick={(event: React.MouseEvent) =>
|
||||
showSelectorAction(
|
||||
event,
|
||||
selectorType,
|
||||
item.group,
|
||||
clearSelectorRef.current
|
||||
clearSelectorRef.current || [],
|
||||
)
|
||||
}
|
||||
themeId={ThemeId.FilterBlockItemTag}
|
||||
>
|
||||
<StyledFilterBlockItemTagText
|
||||
className="filter-text"
|
||||
noSelect={true}
|
||||
noSelect
|
||||
isSelected={item.isSelected}
|
||||
truncate
|
||||
>
|
||||
@ -134,50 +142,52 @@ const FilterBlockItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getToggleItem = (item) => {
|
||||
const getToggleItem = (item: TToggleButtonItem) => {
|
||||
return (
|
||||
<StyledFilterBlockItemToggle key={item.key}>
|
||||
<StyledFilterBlockItemToggleText noSelect={true}>
|
||||
<StyledFilterBlockItemToggleText noSelect>
|
||||
{item.label}
|
||||
</StyledFilterBlockItemToggleText>
|
||||
<StyledFilterBlockItemToggleButton
|
||||
isChecked={item.isSelected}
|
||||
onChange={() => changeFilterValueAction(item.key, item.isSelected)}
|
||||
isChecked={item.isSelected || false}
|
||||
onChange={() =>
|
||||
changeFilterValueAction(item.key, item.isSelected || false)
|
||||
}
|
||||
/>
|
||||
</StyledFilterBlockItemToggle>
|
||||
);
|
||||
};
|
||||
|
||||
const getWithOptionsItem = (item) => {
|
||||
const getWithOptionsItem = (item: TWithOptionItem) => {
|
||||
const selectedOption =
|
||||
item.options.find((option) => option.isSelected) || item.options[0];
|
||||
|
||||
return (
|
||||
<ComboBox
|
||||
id={item.id}
|
||||
className={"combo-item"}
|
||||
className="combo-item"
|
||||
key={item.key}
|
||||
onSelect={(data) =>
|
||||
changeFilterValueAction(
|
||||
data.key,
|
||||
`${data.key}`,
|
||||
data.key === item.options[0].key,
|
||||
false,
|
||||
item.withOptions
|
||||
item.withOptions,
|
||||
)
|
||||
}
|
||||
options={item.options}
|
||||
selectedOption={selectedOption}
|
||||
displaySelectedOption={true}
|
||||
scaled={true}
|
||||
scaledOptions={true}
|
||||
displaySelectedOption
|
||||
scaled
|
||||
scaledOptions
|
||||
isDefaultMode={false}
|
||||
directionY={"bottom"}
|
||||
directionY="bottom"
|
||||
fixedDirection
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getCheckboxItem = (item) => {
|
||||
const getCheckboxItem = (item: TCheckboxItem) => {
|
||||
return (
|
||||
<StyledFilterBlockItemCheckboxContainer key={item.key}>
|
||||
<Checkbox
|
||||
@ -186,14 +196,14 @@ const FilterBlockItem = ({
|
||||
label={item.label}
|
||||
isDisabled={item.isDisabled}
|
||||
onChange={() =>
|
||||
changeFilterValueAction(item.key, item.isSelected, false)
|
||||
changeFilterValueAction(item.key, item.isSelected || false, false)
|
||||
}
|
||||
/>
|
||||
</StyledFilterBlockItemCheckboxContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const getTagItem = (item) => {
|
||||
const getTagItem = (item: TTagItem) => {
|
||||
const isRoomsSelector = item.group === FilterGroups.filterRoom;
|
||||
|
||||
const selectorType = isRoomsSelector
|
||||
@ -216,25 +226,26 @@ const FilterBlockItem = ({
|
||||
|
||||
return (
|
||||
<ColorTheme
|
||||
key={item.key}
|
||||
key={Array.isArray(item.key) ? item.key[0] : item.key}
|
||||
isSelected={item.isSelected}
|
||||
name={`${item.label}-${item.key}`}
|
||||
id={item.id}
|
||||
onClick={
|
||||
item.key === FilterKeys.other
|
||||
? (event) => showSelectorAction(event, selectorType, item.group, [])
|
||||
? (event: React.MouseEvent) =>
|
||||
showSelectorAction(event, selectorType, item.group, [])
|
||||
: () =>
|
||||
changeFilterValueAction(
|
||||
item.key,
|
||||
item.isSelected,
|
||||
item.isMultiSelect
|
||||
item.isMultiSelect,
|
||||
)
|
||||
}
|
||||
themeId={ThemeId.FilterBlockItemTag}
|
||||
>
|
||||
<StyledFilterBlockItemTagText
|
||||
className="filter-text"
|
||||
noSelect={true}
|
||||
noSelect
|
||||
isSelected={item.isSelected}
|
||||
truncate
|
||||
>
|
||||
@ -248,21 +259,23 @@ const FilterBlockItem = ({
|
||||
<StyledFilterBlockItem isFirst={isFirst} withoutHeader={withoutHeader}>
|
||||
{!withoutHeader && (
|
||||
<StyledFilterBlockItemHeader>
|
||||
<Heading size="xsmall">{label}</Heading>
|
||||
<Heading size={HeadingSize.xsmall}>{label}</Heading>
|
||||
</StyledFilterBlockItemHeader>
|
||||
)}
|
||||
|
||||
<StyledFilterBlockItemContent
|
||||
withMultiItems={withMultiItems}
|
||||
withoutHeader={withoutHeader}
|
||||
withoutSeparator={withoutSeparator}
|
||||
>
|
||||
{groupItem.map((item) => {
|
||||
if (item.displaySelectorType) return getSelectorItem(item);
|
||||
if (item.isToggle === true) return getToggleItem(item);
|
||||
if (item.withOptions === true) return getWithOptionsItem(item);
|
||||
if (item.isCheckbox === true) return getCheckboxItem(item);
|
||||
return getTagItem(item);
|
||||
{groupItem.map((item: TGroupItem) => {
|
||||
if ("displaySelectorType" in item && item.displaySelectorType)
|
||||
return getSelectorItem(item);
|
||||
if ("isToggle" in item && item.isToggle) return getToggleItem(item);
|
||||
if ("withOptions" in item && item.withOptions)
|
||||
return getWithOptionsItem(item);
|
||||
if ("isCheckbox" in item && item.isCheckbox)
|
||||
return getCheckboxItem(item);
|
||||
return getTagItem(item as TTagItem);
|
||||
})}
|
||||
</StyledFilterBlockItemContent>
|
||||
{!isLast && !withoutSeparator && <StyledFilterBlockItemSeparator />}
|
@ -1,31 +1,26 @@
|
||||
import React from "react";
|
||||
import FilterReactSvrUrl from "PUBLIC_DIR/images/filter.react.svg?url";
|
||||
|
||||
import { IconButton } from "@docspace/shared/components/icon-button";
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
import { IconButton } from "../../icon-button";
|
||||
import { ColorTheme, ThemeId } from "../../color-theme";
|
||||
|
||||
import { FilterButtonProps } from "../Filter.types";
|
||||
import { StyledButton } from "../Filter.styled";
|
||||
|
||||
import FilterBlock from "./FilterBlock";
|
||||
|
||||
import StyledButton from "./StyledButton";
|
||||
|
||||
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
|
||||
const FilterButton = ({
|
||||
t,
|
||||
onFilter,
|
||||
getFilterData,
|
||||
|
||||
selectedFilterValue,
|
||||
|
||||
filterHeader,
|
||||
selectorLabel,
|
||||
|
||||
isPersonalRoom,
|
||||
isRooms,
|
||||
isAccounts,
|
||||
id,
|
||||
title,
|
||||
currentDeviceType,
|
||||
}) => {
|
||||
userId,
|
||||
}: FilterButtonProps) => {
|
||||
const [showFilterBlock, setShowFilterBlock] = React.useState(false);
|
||||
|
||||
const changeShowFilterBlock = React.useCallback(() => {
|
||||
@ -34,7 +29,12 @@ const FilterButton = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledButton id={id} onClick={changeShowFilterBlock} title={title}>
|
||||
<StyledButton
|
||||
id={id}
|
||||
isOpen={showFilterBlock}
|
||||
onClick={changeShowFilterBlock}
|
||||
title={title}
|
||||
>
|
||||
<IconButton iconName={FilterReactSvrUrl} size={16} />
|
||||
{selectedFilterValue && selectedFilterValue.length > 0 && (
|
||||
<ColorTheme themeId={ThemeId.IndicatorFilterButton} />
|
||||
@ -43,17 +43,15 @@ const FilterButton = ({
|
||||
|
||||
{showFilterBlock && (
|
||||
<FilterBlock
|
||||
t={t}
|
||||
filterHeader={filterHeader}
|
||||
selectedFilterValue={selectedFilterValue}
|
||||
hideFilterBlock={changeShowFilterBlock}
|
||||
getFilterData={getFilterData}
|
||||
onFilter={onFilter}
|
||||
selectorLabel={selectorLabel}
|
||||
isPersonalRoom={isPersonalRoom}
|
||||
isRooms={isRooms}
|
||||
isAccounts={isAccounts}
|
||||
currentDeviceType={currentDeviceType}
|
||||
userId={userId}
|
||||
/>
|
||||
)}
|
||||
</>
|
203
packages/shared/components/filter/sub-components/SortButton.tsx
Normal file
203
packages/shared/components/filter/sub-components/SortButton.tsx
Normal file
@ -0,0 +1,203 @@
|
||||
import React from "react";
|
||||
|
||||
import SortDesc from "PUBLIC_DIR/images/sort.desc.react.svg";
|
||||
import SortReactSvgUrl from "PUBLIC_DIR/images/sort.react.svg?url";
|
||||
|
||||
import { isMobile } from "../../../utils";
|
||||
import { Events } from "../../../enums";
|
||||
|
||||
import { ComboBox, ComboBoxSize } from "../../combobox";
|
||||
import { DropDownItem } from "../../drop-down-item";
|
||||
import { IconButton } from "../../icon-button";
|
||||
import { ViewSelector } from "../../view-selector";
|
||||
import { Text } from "../../text";
|
||||
import { Backdrop } from "../../backdrop";
|
||||
|
||||
import { SortButtonProps, TSortDataItem } from "../Filter.types";
|
||||
import { StyledSortButton } from "../Filter.styled";
|
||||
|
||||
const SortButton = ({
|
||||
id,
|
||||
getSortData,
|
||||
getSelectedSortData,
|
||||
|
||||
onChangeViewAs,
|
||||
view,
|
||||
viewAs,
|
||||
viewSettings,
|
||||
|
||||
onSort,
|
||||
viewSelectorVisible,
|
||||
|
||||
onSortButtonClick,
|
||||
title,
|
||||
}: SortButtonProps) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
const [sortData, setSortData] = React.useState<TSortDataItem[]>([]);
|
||||
const [selectedSortData, setSelectedSortData] = React.useState({
|
||||
sortDirection: "",
|
||||
sortId: "",
|
||||
});
|
||||
|
||||
const getSortDataAction = React.useCallback(() => {
|
||||
const value = getSortData?.();
|
||||
const selectedValue = getSelectedSortData?.();
|
||||
|
||||
const data = value.map((item) => {
|
||||
item.className = "option-item";
|
||||
|
||||
if (selectedValue.sortId === item.key) {
|
||||
item.className += " selected-option-item";
|
||||
item.isSelected = true;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
setSortData(data);
|
||||
|
||||
setSelectedSortData({
|
||||
sortDirection: selectedValue.sortDirection,
|
||||
sortId: selectedValue.sortId,
|
||||
});
|
||||
}, [getSortData, getSelectedSortData]);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener(Events.CHANGE_COLUMN, getSortDataAction);
|
||||
getSortDataAction();
|
||||
|
||||
return () =>
|
||||
window.removeEventListener(Events.CHANGE_COLUMN, getSortDataAction);
|
||||
}, [getSortDataAction]);
|
||||
|
||||
const toggleCombobox = React.useCallback(() => {
|
||||
setIsOpen((val) => !val);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
onSortButtonClick?.(!isOpen);
|
||||
}, [isOpen, onSortButtonClick]);
|
||||
|
||||
const onOptionClick = React.useCallback(
|
||||
(e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>) => {
|
||||
const target = e.target as HTMLDivElement;
|
||||
const key = (target.closest(".option-item") as HTMLDivElement)?.dataset
|
||||
.value;
|
||||
|
||||
let sortDirection = selectedSortData.sortDirection;
|
||||
|
||||
if (key === selectedSortData.sortId) {
|
||||
sortDirection = sortDirection === "desc" ? "asc" : "desc";
|
||||
}
|
||||
|
||||
let data = sortData.map((item) => ({ ...item }));
|
||||
|
||||
data = data.map((item) => {
|
||||
item.className = "option-item";
|
||||
item.isSelected = false;
|
||||
if (key === item.key) {
|
||||
item.className += " selected-option-item";
|
||||
item.isSelected = true;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
setSortData(data);
|
||||
|
||||
setSelectedSortData({
|
||||
sortId: key || "",
|
||||
sortDirection,
|
||||
});
|
||||
|
||||
// toggleCombobox();
|
||||
|
||||
onSort?.(key || "", sortDirection);
|
||||
},
|
||||
[onSort, sortData, selectedSortData],
|
||||
);
|
||||
|
||||
const advancedOptions = (
|
||||
<>
|
||||
{viewSelectorVisible && (
|
||||
<>
|
||||
<DropDownItem noHover className="view-selector-item">
|
||||
<Text fontWeight={600}>{view}</Text>
|
||||
<ViewSelector
|
||||
className="view-selector"
|
||||
onChangeView={onChangeViewAs}
|
||||
viewAs={viewAs}
|
||||
viewSettings={viewSettings}
|
||||
/>
|
||||
</DropDownItem>
|
||||
|
||||
<DropDownItem isSeparator />
|
||||
</>
|
||||
)}
|
||||
{sortData?.map((item) => (
|
||||
<DropDownItem
|
||||
id={item.id}
|
||||
onClick={onOptionClick}
|
||||
className={item.className}
|
||||
key={item.key}
|
||||
data-value={item.key}
|
||||
>
|
||||
<Text fontWeight={600}>{item.label}</Text>
|
||||
<SortDesc
|
||||
className={`option-item__icon${
|
||||
item.isSelected ? " selected-option-item__icon" : ""
|
||||
}`}
|
||||
/>
|
||||
</DropDownItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
let advancedOptionsCount = sortData.length;
|
||||
|
||||
if (viewSelectorVisible) {
|
||||
advancedOptionsCount += 1;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Backdrop
|
||||
visible={isOpen}
|
||||
withBackground={isMobile()}
|
||||
onClick={toggleCombobox}
|
||||
withoutBlur={!isMobile()}
|
||||
/>
|
||||
<StyledSortButton
|
||||
viewAs={viewAs}
|
||||
isDesc={selectedSortData.sortDirection === "desc"}
|
||||
onClick={toggleCombobox}
|
||||
id={id}
|
||||
title={title}
|
||||
>
|
||||
<ComboBox
|
||||
opened={isOpen}
|
||||
onToggle={toggleCombobox}
|
||||
className="sort-combo-box"
|
||||
options={[]}
|
||||
selectedOption={{ key: "", label: "" }}
|
||||
directionX="right"
|
||||
directionY="both"
|
||||
scaled
|
||||
size={ComboBoxSize.content}
|
||||
advancedOptions={advancedOptions}
|
||||
disableIconClick={false}
|
||||
disableItemClick
|
||||
isDefaultMode={false}
|
||||
manualY="102%"
|
||||
advancedOptionsCount={advancedOptionsCount}
|
||||
onSelect={() => {}}
|
||||
>
|
||||
<IconButton iconName={SortReactSvgUrl} size={16} />
|
||||
</ComboBox>
|
||||
</StyledSortButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SortButton);
|
@ -5,7 +5,7 @@ Default section
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import Section from "@docspace/common/components/Section";
|
||||
import Section from "@docspace/shared/components/section";
|
||||
```
|
||||
|
||||
```jsx
|
||||
|
@ -10,9 +10,9 @@ export const SelectedItemPure = (props: SelectedItemProps) => {
|
||||
const {
|
||||
label,
|
||||
onClose,
|
||||
isDisabled,
|
||||
isDisabled = false,
|
||||
onClick,
|
||||
isInline,
|
||||
isInline = true,
|
||||
className,
|
||||
id,
|
||||
propKey,
|
||||
@ -62,11 +62,6 @@ export const SelectedItemPure = (props: SelectedItemProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
SelectedItemPure.defaultProps = {
|
||||
isInline: true,
|
||||
isDisabled: false,
|
||||
};
|
||||
|
||||
const SelectedItem = React.memo(SelectedItemPure);
|
||||
|
||||
export { SelectedItem };
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { FilterGroups } from "../../enums";
|
||||
|
||||
type TLabel = string | React.ReactNode;
|
||||
|
||||
export interface SelectedItemProps {
|
||||
@ -9,14 +11,14 @@ export interface SelectedItemProps {
|
||||
onClose: (
|
||||
propKey: string,
|
||||
label: TLabel,
|
||||
group?: string,
|
||||
group?: string | FilterGroups,
|
||||
e?: React.MouseEvent,
|
||||
) => void;
|
||||
/** Sets a callback function that is triggered when the selected item is clicked */
|
||||
onClick?: (
|
||||
propKey: string,
|
||||
label: TLabel,
|
||||
group?: string,
|
||||
group?: string | FilterGroups,
|
||||
e?: React.MouseEvent<HTMLDivElement>,
|
||||
) => void;
|
||||
/** Sets the button to present a disabled state */
|
||||
|
@ -7,7 +7,7 @@ import ArchiveSvgUrl from "PUBLIC_DIR/images/room.archive.svg?url";
|
||||
import EmptyScreenFilter from "PUBLIC_DIR/images/empty_screen_filter.png";
|
||||
|
||||
import { Selector } from "./Selector";
|
||||
import { SelectorProps, TItem } from "./Selector.types";
|
||||
import { SelectorProps, TSelectorItem } from "./Selector.types";
|
||||
|
||||
const StyledRowLoader = styled.div`
|
||||
width: 100%;
|
||||
@ -61,7 +61,7 @@ function makeName() {
|
||||
}
|
||||
|
||||
const getItems = (count: number) => {
|
||||
const items: TItem[] = [];
|
||||
const items: TSelectorItem[] = [];
|
||||
|
||||
for (let i = 0; i < count / 2; i += 1) {
|
||||
const label = makeName();
|
||||
|
@ -5,7 +5,7 @@ import { Body } from "./sub-components/Body";
|
||||
import { Footer } from "./sub-components/Footer";
|
||||
|
||||
import { StyledSelector } from "./Selector.styled";
|
||||
import { AccessRight, SelectorProps, TItem } from "./Selector.types";
|
||||
import { AccessRight, SelectorProps, TSelectorItem } from "./Selector.types";
|
||||
|
||||
const Selector = ({
|
||||
id,
|
||||
@ -86,8 +86,10 @@ const Selector = ({
|
||||
const [footerVisible, setFooterVisible] = React.useState<boolean>(false);
|
||||
const [isSearch, setIsSearch] = React.useState<boolean>(false);
|
||||
|
||||
const [renderedItems, setRenderedItems] = React.useState<TItem[]>([]);
|
||||
const [newSelectedItems, setNewSelectedItems] = React.useState<TItem[]>([]);
|
||||
const [renderedItems, setRenderedItems] = React.useState<TSelectorItem[]>([]);
|
||||
const [newSelectedItems, setNewSelectedItems] = React.useState<
|
||||
TSelectorItem[]
|
||||
>([]);
|
||||
|
||||
const [newFooterInputValue, setNewFooterInputValue] = React.useState<string>(
|
||||
currentFooterInputValue || "",
|
||||
@ -118,7 +120,7 @@ const Selector = ({
|
||||
);
|
||||
|
||||
const compareSelectedItems = React.useCallback(
|
||||
(newList: TItem[]) => {
|
||||
(newList: TSelectorItem[]) => {
|
||||
let isEqual = true;
|
||||
|
||||
if (selectedItems?.length !== newList.length) {
|
||||
@ -138,8 +140,8 @@ const Selector = ({
|
||||
[selectedItems],
|
||||
);
|
||||
|
||||
const onSelectAction = (item: TItem) => {
|
||||
onSelect({
|
||||
const onSelectAction = (item: TSelectorItem) => {
|
||||
onSelect?.({
|
||||
...item,
|
||||
id: item.id,
|
||||
email: item.email || "",
|
||||
@ -152,7 +154,7 @@ const Selector = ({
|
||||
setRenderedItems((value) => {
|
||||
const idx = value.findIndex((x) => item.id === x.id);
|
||||
|
||||
const newValue = value.map((i: TItem) => ({ ...i }));
|
||||
const newValue = value.map((i: TSelectorItem) => ({ ...i }));
|
||||
|
||||
if (idx === -1) return newValue;
|
||||
|
||||
@ -186,7 +188,7 @@ const Selector = ({
|
||||
setRenderedItems((value) => {
|
||||
const idx = value.findIndex((x) => item.id === x.id);
|
||||
|
||||
const newValue = value.map((i: TItem) => ({
|
||||
const newValue = value.map((i: TSelectorItem) => ({
|
||||
...i,
|
||||
isSelected: false,
|
||||
}));
|
||||
@ -342,7 +344,7 @@ const Selector = ({
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadMoreItems={loadMoreItems}
|
||||
totalItems={totalItems}
|
||||
totalItems={totalItems || 0}
|
||||
isLoading={isLoading}
|
||||
searchLoader={searchLoader}
|
||||
rowLoader={rowLoader}
|
||||
@ -361,7 +363,7 @@ const Selector = ({
|
||||
{(footerVisible || alwaysShowFooter) && (
|
||||
<Footer
|
||||
isMultiSelect={isMultiSelect}
|
||||
acceptButtonLabel={acceptButtonLabel}
|
||||
acceptButtonLabel={acceptButtonLabel || ""}
|
||||
selectedItemsCount={newSelectedItems.length}
|
||||
withCancelButton={withCancelButton}
|
||||
cancelButtonLabel={cancelButtonLabel}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
import { RoomsType } from "../../enums";
|
||||
import { AvatarRole } from "../avatar";
|
||||
|
||||
export type AccessRight = {
|
||||
@ -22,13 +22,13 @@ export interface SelectorProps {
|
||||
searchValue?: string;
|
||||
onSearch?: (value: string, callback?: Function) => void;
|
||||
onClearSearch?: (callback?: Function) => void;
|
||||
items: TItem[];
|
||||
onSelect: (item: TItem) => void;
|
||||
items?: TSelectorItem[];
|
||||
onSelect?: (item: TSelectorItem) => void;
|
||||
isMultiSelect?: boolean;
|
||||
selectedItems?: TItem[];
|
||||
acceptButtonLabel: string;
|
||||
selectedItems?: TSelectorItem[];
|
||||
acceptButtonLabel?: string;
|
||||
onAccept: (
|
||||
selectedItems: TItem[],
|
||||
selectedItems: TSelectorItem[],
|
||||
access: AccessRight | null,
|
||||
fileName: string,
|
||||
isFooterCheckboxChecked: boolean,
|
||||
@ -53,9 +53,13 @@ export interface SelectorProps {
|
||||
hasNextPage?: boolean;
|
||||
isNextPageLoading?: boolean;
|
||||
loadNextPage?:
|
||||
| ((startIndex: number, ...rest: unknown[]) => Promise<void>)
|
||||
| ((
|
||||
startIndex: number,
|
||||
search?: string,
|
||||
...rest: unknown[]
|
||||
) => Promise<void>)
|
||||
| null;
|
||||
totalItems: number;
|
||||
totalItems?: number;
|
||||
isLoading?: boolean;
|
||||
searchLoader?: React.ReactNode;
|
||||
rowLoader?: React.ReactNode;
|
||||
@ -98,8 +102,8 @@ export interface BodyProps {
|
||||
withSearch?: boolean;
|
||||
onSearch: (value: string) => void;
|
||||
onClearSearch: () => void;
|
||||
items: TItem[];
|
||||
onSelect: (item: TItem) => void;
|
||||
items: TSelectorItem[];
|
||||
onSelect?: (item: TSelectorItem) => void;
|
||||
isMultiSelect?: boolean;
|
||||
withSelectAll?: boolean;
|
||||
selectAllLabel?: string;
|
||||
@ -159,9 +163,9 @@ export interface FooterProps {
|
||||
cancelButtonId?: string;
|
||||
}
|
||||
|
||||
export type TItem = {
|
||||
export type TSelectorItem = {
|
||||
key?: string;
|
||||
id?: number | string;
|
||||
id?: string | number;
|
||||
label: string;
|
||||
avatar?: string;
|
||||
icon?: string;
|
||||
@ -171,6 +175,7 @@ export type TItem = {
|
||||
isDisabled?: boolean;
|
||||
color?: string;
|
||||
fileExst?: string;
|
||||
roomType?: RoomsType;
|
||||
};
|
||||
|
||||
export interface SearchProps {
|
||||
@ -181,8 +186,8 @@ export interface SearchProps {
|
||||
}
|
||||
|
||||
export type Data = {
|
||||
items: TItem[];
|
||||
onSelect: (item: TItem) => void;
|
||||
items: TSelectorItem[];
|
||||
onSelect?: (item: TSelectorItem) => void;
|
||||
isMultiSelect: boolean;
|
||||
isItemLoaded: (index: number) => boolean;
|
||||
rowLoader: React.ReactNode;
|
||||
|
@ -1 +1,3 @@
|
||||
export { Selector } from "./Selector";
|
||||
|
||||
export type { SelectorProps, TSelectorItem } from "./Selector.types";
|
||||
|
@ -6,7 +6,7 @@ import { Checkbox } from "../../checkbox";
|
||||
import { RoomIcon } from "../../room-icon";
|
||||
|
||||
import { StyledItem } from "../Selector.styled";
|
||||
import { ItemProps, Data, TItem } from "../Selector.types";
|
||||
import { ItemProps, Data, TSelectorItem } from "../Selector.types";
|
||||
|
||||
const compareFunction = (prevProps: ItemProps, nextProps: ItemProps) => {
|
||||
const prevData = prevProps.data;
|
||||
@ -34,7 +34,7 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
const isLoaded = isItemLoaded(index);
|
||||
|
||||
const renderItem = () => {
|
||||
const item: TItem = items[index];
|
||||
const item: TSelectorItem = items[index];
|
||||
|
||||
if (!item || (item && !item.id))
|
||||
return <div style={style}>{rowLoader}</div>;
|
||||
@ -47,7 +47,7 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
const isLogo = !!icon || defaultIcon;
|
||||
|
||||
const onChangeAction = () => {
|
||||
onSelect(item);
|
||||
onSelect?.(item);
|
||||
};
|
||||
|
||||
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
@ -58,7 +58,7 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
)
|
||||
return;
|
||||
|
||||
onSelect(item);
|
||||
onSelect?.(item);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -7,7 +7,7 @@ export type TViewSelectorOption = {
|
||||
|
||||
export interface ViewSelectorProps {
|
||||
/** Disables the button default functionality */
|
||||
isDisabled: boolean;
|
||||
isDisabled?: boolean;
|
||||
/** Sets a callback function that is triggered when the button is clicked */
|
||||
onChangeView: (view: string) => void;
|
||||
/** Array that contains the view settings */
|
||||
@ -15,5 +15,8 @@ export interface ViewSelectorProps {
|
||||
/** Current application view */
|
||||
viewAs: string;
|
||||
/** Displays only available selector options */
|
||||
isFilter: boolean;
|
||||
isFilter?: boolean;
|
||||
className?: string;
|
||||
id?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { useState, useEffect, Dispatch, useCallback, useRef } from "react";
|
||||
|
||||
function useLoadingWithTimeout<S = undefined>(
|
||||
timeout: number
|
||||
timeout: number,
|
||||
): [S | undefined, Dispatch<S | undefined>];
|
||||
|
||||
function useLoadingWithTimeout<S extends boolean>(
|
||||
timeout: number,
|
||||
initialState: S
|
||||
initialState: S,
|
||||
): [S, Dispatch<S>];
|
||||
|
||||
function useLoadingWithTimeout<S extends boolean | undefined = undefined>(
|
||||
timeout: number,
|
||||
initialState?: S
|
||||
initialState?: S,
|
||||
) {
|
||||
const [state, setState] = useState<S | undefined>(initialState);
|
||||
const timerRef = useRef<number>();
|
||||
@ -21,20 +21,23 @@ function useLoadingWithTimeout<S extends boolean | undefined = undefined>(
|
||||
timerRef.current = undefined;
|
||||
}, []);
|
||||
|
||||
const setStateWithTimeout: Dispatch<S | undefined> = useCallback((value) => {
|
||||
cleanTimer();
|
||||
if (value) {
|
||||
timerRef.current = window.setTimeout(() => {
|
||||
const setStateWithTimeout: Dispatch<S | undefined> = useCallback(
|
||||
(value) => {
|
||||
cleanTimer();
|
||||
if (value) {
|
||||
timerRef.current = window.setTimeout(() => {
|
||||
setState(value);
|
||||
}, timeout);
|
||||
} else {
|
||||
setState(value);
|
||||
}, timeout);
|
||||
} else {
|
||||
setState(value);
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
},
|
||||
[cleanTimer, timeout],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => cleanTimer();
|
||||
}, []);
|
||||
}, [cleanTimer]);
|
||||
|
||||
return [state, setStateWithTimeout];
|
||||
}
|
18
packages/shared/selectors/People/PeopleSelector.types.ts
Normal file
18
packages/shared/selectors/People/PeopleSelector.types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { SelectorProps } from "../../components/selector";
|
||||
import PeopleFilter from "../../api/people/filter";
|
||||
|
||||
export interface UserTooltipProps {
|
||||
avatarUrl: string;
|
||||
label: string;
|
||||
email: string;
|
||||
position: string;
|
||||
}
|
||||
|
||||
export interface PeopleSelectorProps extends SelectorProps {
|
||||
filter?: PeopleFilter | Function;
|
||||
excludeItems?: string[];
|
||||
currentUserId: string;
|
||||
withOutCurrentAuthorizedUser?: boolean;
|
||||
withAbilityCreateRoomUsers?: boolean;
|
||||
filterUserId?: string;
|
||||
}
|
294
packages/shared/selectors/People/index.tsx
Normal file
294
packages/shared/selectors/People/index.tsx
Normal file
@ -0,0 +1,294 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import DefaultUserPhoto from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
|
||||
import EmptyScreenPersonsSvgUrl from "PUBLIC_DIR/images/empty_screen_persons.svg?url";
|
||||
import CatalogAccountsReactSvgUrl from "PUBLIC_DIR/images/catalog.accounts.react.svg?url";
|
||||
import EmptyScreenPersonsSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_persons_dark.svg?url";
|
||||
|
||||
import { Selector } from "../../components/selector";
|
||||
import { TSelectorItem } from "../../components/selector/Selector.types";
|
||||
import { EmployeeStatus } from "../../enums";
|
||||
import { TTranslation } from "../../types";
|
||||
import { LOADER_TIMEOUT } from "../../constants";
|
||||
import { getUserRole } from "../../utils/common";
|
||||
import Filter from "../../api/people/filter";
|
||||
import { getUserList } from "../../api/people";
|
||||
import { TUser } from "../../api/people/types";
|
||||
import useLoadingWithTimeout from "../../hooks/useLoadingWithTimeout";
|
||||
import { RowLoader, SearchLoader } from "../../skeletons/selector";
|
||||
import { AvatarRole } from "../../components/avatar";
|
||||
|
||||
import { PeopleSelectorProps } from "./PeopleSelector.types";
|
||||
|
||||
const PeopleSelector = ({
|
||||
acceptButtonLabel,
|
||||
accessRights,
|
||||
cancelButtonLabel,
|
||||
className,
|
||||
emptyScreenDescription,
|
||||
emptyScreenHeader,
|
||||
headerLabel,
|
||||
id,
|
||||
isMultiSelect,
|
||||
items,
|
||||
onAccept,
|
||||
|
||||
onBackClick,
|
||||
onCancel,
|
||||
|
||||
searchEmptyScreenDescription,
|
||||
searchEmptyScreenHeader,
|
||||
searchPlaceholder,
|
||||
selectAllIcon = CatalogAccountsReactSvgUrl,
|
||||
selectAllLabel,
|
||||
selectedAccessRight,
|
||||
selectedItems,
|
||||
style,
|
||||
withAccessRights,
|
||||
withCancelButton,
|
||||
withSelectAll,
|
||||
filter,
|
||||
excludeItems = [],
|
||||
currentUserId,
|
||||
|
||||
withOutCurrentAuthorizedUser,
|
||||
withAbilityCreateRoomUsers,
|
||||
withFooterCheckbox,
|
||||
footerCheckboxLabel,
|
||||
isChecked,
|
||||
setIsChecked,
|
||||
filterUserId,
|
||||
}: PeopleSelectorProps) => {
|
||||
const { t }: { t: TTranslation } = useTranslation([
|
||||
"PeopleSelector",
|
||||
"PeopleTranslations",
|
||||
"People",
|
||||
"Common",
|
||||
]);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const [itemsList, setItemsList] = useState(items);
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const [total, setTotal] = useState<number>(-1);
|
||||
const [hasNextPage, setHasNextPage] = useState(true);
|
||||
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
|
||||
const [isLoading, setIsLoading] = useLoadingWithTimeout<boolean>(
|
||||
LOADER_TIMEOUT,
|
||||
false,
|
||||
);
|
||||
|
||||
const moveCurrentUserToTopOfList = useCallback(
|
||||
(listUser: TSelectorItem[]) => {
|
||||
const currentUserIndex = listUser.findIndex(
|
||||
(user) => user.id === currentUserId,
|
||||
);
|
||||
|
||||
// return if the current user is already at the top of the list or not found
|
||||
if (currentUserIndex < 1) return listUser;
|
||||
|
||||
const [currentUser] = listUser.splice(currentUserIndex, 1);
|
||||
|
||||
listUser.splice(0, 0, currentUser);
|
||||
|
||||
return listUser;
|
||||
},
|
||||
[currentUserId],
|
||||
);
|
||||
|
||||
const removeCurrentUserFromList = useCallback(
|
||||
(listUser: TSelectorItem[]) => {
|
||||
if (filterUserId) {
|
||||
return listUser.filter((user) => user.id !== filterUserId);
|
||||
}
|
||||
return listUser.filter((user) => user.id !== currentUserId);
|
||||
},
|
||||
[currentUserId, filterUserId],
|
||||
);
|
||||
|
||||
const toListItem = (item: TUser) => {
|
||||
const {
|
||||
id: userId,
|
||||
email,
|
||||
avatar,
|
||||
displayName,
|
||||
hasAvatar,
|
||||
isOwner,
|
||||
isAdmin,
|
||||
isVisitor,
|
||||
isCollaborator,
|
||||
} = item;
|
||||
|
||||
const role = getUserRole(item);
|
||||
|
||||
const userAvatar = hasAvatar ? avatar : DefaultUserPhoto;
|
||||
|
||||
return {
|
||||
id: userId,
|
||||
email,
|
||||
avatar: userAvatar,
|
||||
label: displayName || email,
|
||||
role: AvatarRole[role],
|
||||
isOwner,
|
||||
isAdmin,
|
||||
isVisitor,
|
||||
isCollaborator,
|
||||
hasAvatar,
|
||||
};
|
||||
};
|
||||
|
||||
const loadNextPage = useCallback(
|
||||
async (startIndex: number, search = searchValue) => {
|
||||
const pageCount = 100;
|
||||
|
||||
setIsNextPageLoading(true);
|
||||
|
||||
if (startIndex === 0) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
|
||||
const currentFilter =
|
||||
typeof filter === "function" ? filter() : filter ?? Filter.getDefault();
|
||||
|
||||
currentFilter.page = startIndex / pageCount;
|
||||
currentFilter.pageCount = pageCount;
|
||||
|
||||
if (search.length) {
|
||||
currentFilter.search = search;
|
||||
}
|
||||
|
||||
const response = await getUserList(currentFilter);
|
||||
|
||||
let newItems = startIndex && itemsList ? itemsList : [];
|
||||
let totalDifferent = startIndex ? response.total - total : 0;
|
||||
|
||||
const data = response.items
|
||||
.filter((item) => {
|
||||
const excludeUser =
|
||||
withAbilityCreateRoomUsers &&
|
||||
((!item.isAdmin && !item.isOwner && !item.isRoomAdmin) ||
|
||||
item.status === EmployeeStatus.Disabled);
|
||||
|
||||
if (excludeItems.includes(item.id) || excludeUser) {
|
||||
totalDifferent += 1;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map((item) => toListItem(item));
|
||||
|
||||
const tempItems = [...newItems, ...data];
|
||||
|
||||
newItems = withOutCurrentAuthorizedUser
|
||||
? removeCurrentUserFromList(tempItems)
|
||||
: moveCurrentUserToTopOfList(tempItems);
|
||||
|
||||
const newTotal = withOutCurrentAuthorizedUser
|
||||
? response.total - totalDifferent - 1
|
||||
: response.total - totalDifferent;
|
||||
|
||||
setHasNextPage(newItems.length < newTotal);
|
||||
setItemsList(newItems);
|
||||
setTotal(newTotal);
|
||||
|
||||
setIsNextPageLoading(false);
|
||||
setIsLoading(false);
|
||||
},
|
||||
[
|
||||
excludeItems,
|
||||
filter,
|
||||
itemsList,
|
||||
moveCurrentUserToTopOfList,
|
||||
removeCurrentUserFromList,
|
||||
searchValue,
|
||||
setIsLoading,
|
||||
total,
|
||||
withAbilityCreateRoomUsers,
|
||||
withOutCurrentAuthorizedUser,
|
||||
],
|
||||
);
|
||||
|
||||
const onSearch = (value: string) => {
|
||||
setSearchValue(value);
|
||||
loadNextPage(0, value);
|
||||
};
|
||||
|
||||
const onClearSearch = () => {
|
||||
setSearchValue("");
|
||||
loadNextPage(0, "");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadNextPage(0);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const emptyScreenImage = theme.isBase
|
||||
? EmptyScreenPersonsSvgUrl
|
||||
: EmptyScreenPersonsSvgDarkUrl;
|
||||
|
||||
return (
|
||||
<Selector
|
||||
id={id}
|
||||
className={className}
|
||||
style={style}
|
||||
headerLabel={headerLabel || t("ListAccounts")}
|
||||
onBackClick={onBackClick}
|
||||
searchPlaceholder={searchPlaceholder || t("Common:Search")}
|
||||
searchValue={searchValue}
|
||||
onSearch={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
items={itemsList}
|
||||
isMultiSelect={isMultiSelect}
|
||||
selectedItems={selectedItems}
|
||||
acceptButtonLabel={acceptButtonLabel || t("Common:SelectAction")}
|
||||
onAccept={onAccept}
|
||||
withSelectAll={withSelectAll}
|
||||
selectAllLabel={selectAllLabel || t("AllAccounts")}
|
||||
selectAllIcon={selectAllIcon}
|
||||
withAccessRights={withAccessRights}
|
||||
accessRights={accessRights}
|
||||
selectedAccessRight={selectedAccessRight}
|
||||
withCancelButton={withCancelButton}
|
||||
cancelButtonLabel={cancelButtonLabel || t("Common:CancelButton")}
|
||||
onCancel={onCancel}
|
||||
emptyScreenImage={emptyScreenImage}
|
||||
emptyScreenHeader={emptyScreenHeader || t("EmptyHeader")}
|
||||
emptyScreenDescription={emptyScreenDescription || t("EmptyDescription")}
|
||||
searchEmptyScreenImage={emptyScreenImage}
|
||||
searchEmptyScreenHeader={
|
||||
searchEmptyScreenHeader || t("People:NotFoundUsers")
|
||||
}
|
||||
searchEmptyScreenDescription={
|
||||
searchEmptyScreenDescription || t("People:NotFoundUsersDescription")
|
||||
}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
totalItems={total}
|
||||
isLoading={isLoading}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
footerCheckboxLabel={footerCheckboxLabel}
|
||||
isChecked={isChecked}
|
||||
setIsChecked={setIsChecked}
|
||||
searchLoader={<SearchLoader />}
|
||||
isSearchLoading={isLoading}
|
||||
rowLoader={<RowLoader isUser isContainer={isLoading} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// const ExtendedPeopleSelector = inject(({ auth }) => {
|
||||
// return {
|
||||
// theme: auth.settingsStore.theme,
|
||||
// currentUserId: auth.userStore.user.id,
|
||||
// };
|
||||
// })(observer(PeopleSelector));
|
||||
|
||||
// export default (props) => (
|
||||
|
||||
// );
|
||||
|
||||
export default PeopleSelector;
|
@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import { useTheme } from "styled-components";
|
||||
import { Avatar, AvatarRole, AvatarSize } from "../../../components/avatar";
|
||||
import { Text } from "../../../components/text";
|
||||
|
||||
import StyledUserTooltip from "../PeopleSelector.styled";
|
||||
import { UserTooltipProps } from "../PeopleSelector.types";
|
||||
|
||||
const UserTooltip = ({
|
||||
avatarUrl,
|
||||
label,
|
||||
email,
|
||||
position,
|
||||
}: UserTooltipProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledUserTooltip>
|
||||
<div className="block-avatar">
|
||||
<Avatar
|
||||
className="user-avatar"
|
||||
size={AvatarSize.min}
|
||||
role={AvatarRole.user}
|
||||
source={avatarUrl}
|
||||
userName=""
|
||||
editing={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="block-info">
|
||||
<Text isBold fontSize="13px" fontWeight={600} truncate title={label}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text
|
||||
color={theme.peopleSelector.textColor}
|
||||
fontSize="13px"
|
||||
className="email-text"
|
||||
truncate
|
||||
title={email}
|
||||
>
|
||||
{email}
|
||||
</Text>
|
||||
<Text fontSize="13px" fontWeight={600} truncate title={position}>
|
||||
{position}
|
||||
</Text>
|
||||
</div>
|
||||
</StyledUserTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserTooltip;
|
16
packages/shared/selectors/Room/RoomSelector.types.ts
Normal file
16
packages/shared/selectors/Room/RoomSelector.types.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { SelectorProps } from "../../components/selector";
|
||||
import { TLogo } from "../../api/rooms/types";
|
||||
import { RoomsType } from "../../enums";
|
||||
|
||||
export interface RoomSelectorProps extends SelectorProps {
|
||||
excludeItems?: number[];
|
||||
}
|
||||
|
||||
export type TItem = {
|
||||
id: number;
|
||||
label: string;
|
||||
icon: string;
|
||||
color: string | undefined;
|
||||
logo: TLogo;
|
||||
roomType: RoomsType;
|
||||
};
|
14
packages/shared/selectors/Room/RoomSelector.utils.ts
Normal file
14
packages/shared/selectors/Room/RoomSelector.utils.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { TRoom } from "../../api/rooms/types";
|
||||
|
||||
export const convertToItems = (folders: TRoom[]) => {
|
||||
const items = folders.map((folder) => {
|
||||
const { id, title, roomType, logo } = folder;
|
||||
|
||||
const icon = logo.medium;
|
||||
const color = logo.color;
|
||||
|
||||
return { id, label: title, icon, color, roomType };
|
||||
});
|
||||
|
||||
return items;
|
||||
};
|
@ -1,61 +1,26 @@
|
||||
import EmptyScreenCorporateSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url";
|
||||
import React from "react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import api from "@docspace/shared/api";
|
||||
import RoomsFilter from "@docspace/shared/api/rooms/filter";
|
||||
import { RoomsType } from "@docspace/shared/enums";
|
||||
import { iconSize32 } from "@docspace/shared/utils/image-helpers";
|
||||
import EmptyScreenCorporateSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url";
|
||||
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { Selector, TSelectorItem } from "../../components/selector";
|
||||
import { RowLoader, SearchLoader } from "../../skeletons/selector";
|
||||
import api from "../../api";
|
||||
import RoomsFilter from "../../api/rooms/filter";
|
||||
|
||||
import { Selector } from "@docspace/shared/components/selector";
|
||||
import { TTranslation } from "../../types";
|
||||
|
||||
const pageCount = 100;
|
||||
import { RoomSelectorProps } from "./RoomSelector.types";
|
||||
import { convertToItems } from "./RoomSelector.utils";
|
||||
|
||||
const getRoomLogo = (roomType) => {
|
||||
let path = "";
|
||||
switch (roomType) {
|
||||
case RoomsType.CustomRoom:
|
||||
path = "custom.svg";
|
||||
break;
|
||||
case RoomsType.FillingFormsRoom:
|
||||
path = "filling.form.svg";
|
||||
break;
|
||||
case RoomsType.EditingRoom:
|
||||
path = "editing.svg";
|
||||
break;
|
||||
case RoomsType.ReadOnlyRoom:
|
||||
path = "view.only.svg";
|
||||
break;
|
||||
case RoomsType.ReviewRoom:
|
||||
path = "review.svg";
|
||||
break;
|
||||
}
|
||||
|
||||
return iconSize32.get(path);
|
||||
};
|
||||
|
||||
const convertToItems = (folders) => {
|
||||
const items = folders.map((folder) => {
|
||||
const { id, title, roomType, logo } = folder;
|
||||
|
||||
const icon = logo.medium ? logo.medium : getRoomLogo(roomType);
|
||||
const color = logo.color;
|
||||
|
||||
return { id, label: title, icon, color, logo, roomType };
|
||||
});
|
||||
|
||||
return items;
|
||||
};
|
||||
const PAGE_COUNT = 100;
|
||||
|
||||
const RoomSelector = ({
|
||||
t,
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
|
||||
excludeItems,
|
||||
excludeItems = [],
|
||||
|
||||
headerLabel,
|
||||
onBackClick,
|
||||
@ -91,30 +56,32 @@ const RoomSelector = ({
|
||||
searchEmptyScreenImage,
|
||||
searchEmptyScreenHeader,
|
||||
searchEmptyScreenDescription,
|
||||
}) => {
|
||||
}: RoomSelectorProps) => {
|
||||
const { t }: { t: TTranslation } = useTranslation(["RoomSelector", "Common"]);
|
||||
|
||||
const [isFirstLoad, setIsFirstLoad] = React.useState(true);
|
||||
const [searchValue, setSearchValue] = React.useState("");
|
||||
const [hasNextPage, setHasNextPage] = React.useState(false);
|
||||
const [isNextPageLoading, setIsNextPageLoading] = React.useState(false);
|
||||
|
||||
const [total, setTotal] = React.useState(0);
|
||||
const [total, setTotal] = React.useState(-1);
|
||||
|
||||
const [items, setItems] = React.useState([]);
|
||||
const [items, setItems] = React.useState<TSelectorItem[]>([]);
|
||||
|
||||
const onSearchAction = React.useCallback(
|
||||
(value) => {
|
||||
onSearch && onSearch(value);
|
||||
(value: string) => {
|
||||
onSearch?.(value);
|
||||
setSearchValue(() => {
|
||||
setIsFirstLoad(true);
|
||||
|
||||
return value;
|
||||
});
|
||||
},
|
||||
[onSearch]
|
||||
[onSearch],
|
||||
);
|
||||
|
||||
const onClearSearchAction = React.useCallback(() => {
|
||||
onClearSearch && onClearSearch();
|
||||
onClearSearch?.();
|
||||
setSearchValue(() => {
|
||||
setIsFirstLoad(true);
|
||||
|
||||
@ -123,50 +90,51 @@ const RoomSelector = ({
|
||||
}, [onClearSearch]);
|
||||
|
||||
const onLoadNextPage = React.useCallback(
|
||||
(startIndex) => {
|
||||
async (startIndex: number) => {
|
||||
setIsNextPageLoading(true);
|
||||
|
||||
const page = startIndex / pageCount;
|
||||
const page = startIndex / PAGE_COUNT;
|
||||
|
||||
const filter = RoomsFilter.getDefault();
|
||||
|
||||
filter.page = page;
|
||||
filter.pageCount = pageCount;
|
||||
filter.pageCount = PAGE_COUNT;
|
||||
|
||||
filter.filterValue = searchValue ? searchValue : null;
|
||||
filter.filterValue = searchValue || null;
|
||||
|
||||
api.rooms
|
||||
.getRooms(filter)
|
||||
.then(({ folders, total, count }) => {
|
||||
const rooms = convertToItems(folders);
|
||||
const {
|
||||
folders,
|
||||
total: totalCount,
|
||||
count,
|
||||
} = await api.rooms.getRooms(filter);
|
||||
|
||||
const itemList = rooms.filter((x) => !excludeItems.includes(x.id));
|
||||
const rooms = convertToItems(folders);
|
||||
|
||||
setHasNextPage(count === pageCount);
|
||||
const itemList = rooms.filter((x) => !excludeItems.includes(x.id));
|
||||
|
||||
if (isFirstLoad) {
|
||||
setTotal(total);
|
||||
setItems(itemList);
|
||||
} else {
|
||||
setItems((value) => [...value, ...itemList]);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (isFirstLoad) {
|
||||
setTimeout(() => {
|
||||
setIsFirstLoad(false);
|
||||
}, 500);
|
||||
}
|
||||
setHasNextPage(count === PAGE_COUNT);
|
||||
|
||||
setIsNextPageLoading(false);
|
||||
});
|
||||
if (isFirstLoad) {
|
||||
setTotal(totalCount);
|
||||
setItems(itemList);
|
||||
} else {
|
||||
setItems((value) => [...value, ...itemList]);
|
||||
}
|
||||
|
||||
if (isFirstLoad) {
|
||||
setTimeout(() => {
|
||||
setIsFirstLoad(false);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
setIsNextPageLoading(false);
|
||||
},
|
||||
[isFirstLoad, excludeItems, searchValue]
|
||||
[isFirstLoad, excludeItems, searchValue],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
onLoadNextPage(0);
|
||||
}, [searchValue]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Selector
|
||||
@ -216,9 +184,9 @@ const RoomSelector = ({
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={onLoadNextPage}
|
||||
isLoading={isFirstLoad}
|
||||
searchLoader={<Loaders.SelectorSearchLoader />}
|
||||
searchLoader={<SearchLoader />}
|
||||
rowLoader={
|
||||
<Loaders.SelectorRowLoader
|
||||
<RowLoader
|
||||
isMultiSelect={isMultiSelect}
|
||||
isContainer={isFirstLoad}
|
||||
isUser={false}
|
||||
@ -228,6 +196,4 @@ const RoomSelector = ({
|
||||
);
|
||||
};
|
||||
|
||||
RoomSelector.defaultProps = { excludeItems: [] };
|
||||
|
||||
export default withTranslation(["RoomSelector", "Common"])(RoomSelector);
|
||||
export default RoomSelector;
|
@ -1,6 +1,6 @@
|
||||
import styled, { css } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { mobile } from "@docspace/shared/utils";
|
||||
import { mobile } from "../../../utils";
|
||||
|
||||
const StyledFilter = styled.div`
|
||||
width: 100%;
|
7
packages/shared/skeletons/filter/Filter/Filter.types.ts
Normal file
7
packages/shared/skeletons/filter/Filter/Filter.types.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { RectangleSkeletonProps } from "../../rectangle";
|
||||
|
||||
export interface FilterLoaderProps extends RectangleSkeletonProps {
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import StyledFilter from "./StyledFilterLoader";
|
||||
import { RectangleSkeleton } from "@docspace/shared/skeletons";
|
||||
|
||||
const FilterLoader = ({ id, className, style, ...rest }) => {
|
||||
import StyledFilter from "./Filter.styled";
|
||||
import { RectangleSkeleton } from "../../rectangle";
|
||||
import { FilterLoaderProps } from "./Filter.types";
|
||||
|
||||
const FilterLoader = ({ id, className, style, ...rest }: FilterLoaderProps) => {
|
||||
const {
|
||||
title,
|
||||
height,
|
||||
@ -44,16 +45,4 @@ const FilterLoader = ({ id, className, style, ...rest }) => {
|
||||
);
|
||||
};
|
||||
|
||||
FilterLoader.propTypes = {
|
||||
id: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
|
||||
FilterLoader.defaultProps = {
|
||||
id: undefined,
|
||||
className: undefined,
|
||||
style: undefined,
|
||||
};
|
||||
|
||||
export default FilterLoader;
|
3
packages/shared/skeletons/filter/Filter/index.ts
Normal file
3
packages/shared/skeletons/filter/Filter/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import FilterLoader from "./FilterLoader";
|
||||
|
||||
export { FilterLoader };
|
@ -1,6 +1,6 @@
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
import { Base } from "@docspace/shared/themes";
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
@ -11,7 +11,7 @@ const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
const StyledBlock = styled.div`
|
||||
const StyledBlock = styled.div<{ isLast?: boolean }>`
|
||||
padding: 12px 0 16px;
|
||||
|
||||
margin-bottom: 4px;
|
||||
@ -31,7 +31,7 @@ const StyledBlock = styled.div`
|
||||
css`
|
||||
border-bottom: 1px solid;
|
||||
|
||||
border-color: ${(props) => props.theme.filterInput.filter.separatorColor};
|
||||
border-color: ${props.theme.filterInput.filter.separatorColor};
|
||||
`}
|
||||
|
||||
.row-loader {
|
@ -0,0 +1,8 @@
|
||||
import { RectangleSkeletonProps } from "../../rectangle";
|
||||
|
||||
export interface FilterBlockProps extends RectangleSkeletonProps {
|
||||
id?: string;
|
||||
className?: string;
|
||||
isRooms?: boolean;
|
||||
isAccounts?: boolean;
|
||||
}
|
311
packages/shared/skeletons/filter/FilterBlock/index.tsx
Normal file
311
packages/shared/skeletons/filter/FilterBlock/index.tsx
Normal file
@ -0,0 +1,311 @@
|
||||
import React from "react";
|
||||
import { RoomsType } from "@docspace/shared/enums";
|
||||
import { RoomsTypeValues } from "@docspace/shared/utils/common";
|
||||
import { RectangleSkeleton } from "@docspace/shared/skeletons";
|
||||
|
||||
import { StyledBlock, StyledContainer } from "./FilterBlock.styled";
|
||||
import { FilterBlockProps } from "./FilterBlock.types";
|
||||
|
||||
const FilterBlockLoader = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
isRooms,
|
||||
isAccounts,
|
||||
|
||||
...rest
|
||||
}: FilterBlockProps) => {
|
||||
const roomTypeLoader = isRooms ? (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
key={RoomsType.EditingRoom}
|
||||
width="98"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
key={RoomsType.CustomRoom}
|
||||
width="89"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<StyledContainer id={id} className={className} style={style} {...rest}>
|
||||
{!isRooms && !isAccounts && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width="50"
|
||||
height="16"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="100%"
|
||||
height="32"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width="16"
|
||||
height="16"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="137"
|
||||
height="20"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
</div>
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
{!isAccounts && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width="51"
|
||||
height="16"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width="51"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="68"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item"
|
||||
/>
|
||||
</div>
|
||||
{isRooms && (
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width="16"
|
||||
height="16"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="137"
|
||||
height="20"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
{(isRooms || isAccounts) && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width="50"
|
||||
height="16"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<div className="row-loader">
|
||||
{isAccounts ? (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width="67"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="80"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="83"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
</>
|
||||
) : isRooms ? (
|
||||
roomTypeLoader
|
||||
) : null}
|
||||
</div>
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
{isAccounts && (
|
||||
<StyledBlock>
|
||||
<RectangleSkeleton
|
||||
width="50"
|
||||
height="16"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<div className="row-loader">
|
||||
<RectangleSkeleton
|
||||
width="114"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="110"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="108"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="59"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
</div>
|
||||
</StyledBlock>
|
||||
)}
|
||||
|
||||
<StyledBlock isLast>
|
||||
<RectangleSkeleton
|
||||
width="50"
|
||||
height="16"
|
||||
borderRadius="3"
|
||||
className="loader-item"
|
||||
/>
|
||||
<div className="row-loader">
|
||||
{isAccounts ? (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width="57"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="57"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
</>
|
||||
) : isRooms ? (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width="67"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="73"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="67"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="74"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="65"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="72"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RectangleSkeleton
|
||||
width="73"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="99"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="114"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="112"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="130"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="66"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="81"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="74"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
<RectangleSkeleton
|
||||
width="68"
|
||||
height="28"
|
||||
borderRadius="16"
|
||||
className="loader-item tag-item"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</StyledBlock>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterBlockLoader;
|
4
packages/shared/skeletons/filter/index.ts
Normal file
4
packages/shared/skeletons/filter/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { FilterLoader } from "./Filter";
|
||||
import FilterBlockLoader from "./FilterBlock";
|
||||
|
||||
export { FilterBlockLoader, FilterLoader };
|
@ -12,4 +12,5 @@ export interface RectangleSkeletonProps {
|
||||
foregroundOpacity?: number;
|
||||
speed?: number;
|
||||
animate?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
import { RectangleSkeleton } from "@docspace/shared/skeletons";
|
||||
import { RectangleSkeleton, RectangleSkeletonProps } from "../rectangle";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
@ -13,7 +13,7 @@ const StyledContainer = styled.div`
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledItem = styled.div`
|
||||
const StyledItem = styled.div<{ isUser?: boolean }>`
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
@ -52,7 +52,18 @@ const Divider = styled.div`
|
||||
border-bottom: ${(props) => props.theme.selector.border};
|
||||
`;
|
||||
|
||||
const SelectorRowLoader = ({
|
||||
interface RowLoaderProps extends RectangleSkeletonProps {
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
isMultiSelect?: boolean;
|
||||
isContainer?: boolean;
|
||||
isUser?: boolean;
|
||||
withAllSelect?: boolean;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
const RowLoader = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
@ -62,30 +73,21 @@ const SelectorRowLoader = ({
|
||||
withAllSelect,
|
||||
count = 5,
|
||||
...rest
|
||||
}) => {
|
||||
const getRowItem = (key) => {
|
||||
}: RowLoaderProps) => {
|
||||
const getRowItem = (key: number) => {
|
||||
return (
|
||||
<StyledItem
|
||||
id={id}
|
||||
className={className}
|
||||
style={style}
|
||||
isMultiSelect={isMultiSelect}
|
||||
isUser={isUser}
|
||||
key={key}
|
||||
{...rest}
|
||||
>
|
||||
<RectangleSkeleton
|
||||
className={"avatar"}
|
||||
width={"32px"}
|
||||
height={"32px"}
|
||||
/>
|
||||
<RectangleSkeleton width={"212px"} height={"16px"} />
|
||||
<RectangleSkeleton className="avatar" width="32px" height="32px" />
|
||||
<RectangleSkeleton width="212px" height="16px" />
|
||||
{isMultiSelect && (
|
||||
<RectangleSkeleton
|
||||
className={"checkbox"}
|
||||
width={"16px"}
|
||||
height={"16px"}
|
||||
/>
|
||||
<RectangleSkeleton className="checkbox" width="16px" height="16px" />
|
||||
)}
|
||||
</StyledItem>
|
||||
);
|
||||
@ -93,7 +95,7 @@ const SelectorRowLoader = ({
|
||||
|
||||
const getRowItems = () => {
|
||||
const rows = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
rows.push(getRowItem(i));
|
||||
}
|
||||
|
||||
@ -111,8 +113,8 @@ const SelectorRowLoader = ({
|
||||
{getRowItems()}
|
||||
</StyledContainer>
|
||||
) : (
|
||||
getRowItem()
|
||||
getRowItem(0)
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectorRowLoader;
|
||||
export default RowLoader;
|
27
packages/shared/skeletons/selector/Search.tsx
Normal file
27
packages/shared/skeletons/selector/Search.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import { RectangleSkeleton, RectangleSkeletonProps } from "../rectangle";
|
||||
|
||||
interface SearchLoaderProps extends RectangleSkeletonProps {
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const SearchLoader = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
|
||||
...rest
|
||||
}: SearchLoaderProps) => {
|
||||
return (
|
||||
<RectangleSkeleton
|
||||
width="calc(100% - 16px)"
|
||||
height="32px"
|
||||
style={{ padding: "0 0 0 16px", marginBottom: "8px", ...style }}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchLoader;
|
4
packages/shared/skeletons/selector/index.ts
Normal file
4
packages/shared/skeletons/selector/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import RowLoader from "./Row";
|
||||
import SearchLoader from "./Search";
|
||||
|
||||
export { SearchLoader, RowLoader };
|
@ -14,6 +14,7 @@
|
||||
"hooks",
|
||||
"api",
|
||||
"sw",
|
||||
"selectors",
|
||||
// add all files in which you see
|
||||
// the "parserOptions.project" error
|
||||
".eslintrc.cjs",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user