Merge branch 'feature/shared-filter' into feature/shared-article

This commit is contained in:
Timofey Boyko 2024-01-19 10:47:08 +03:00
commit 8b68123fba
104 changed files with 2559 additions and 4106 deletions

View File

@ -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}

View File

@ -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;

View File

@ -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>
);

View File

@ -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;

View File

@ -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>
)}

View File

@ -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")}

View File

@ -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}

View File

@ -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)));

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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,

View File

@ -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}
/>
);
};

View File

@ -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";

View File

@ -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 />
);
};

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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,

View File

@ -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";

View File

@ -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";

View File

@ -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":

View File

@ -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 };

View File

@ -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));

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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>
));

View File

@ -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 |

View File

@ -1 +0,0 @@
export default from "./FilterLoader";

View File

@ -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;

View File

@ -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,

View File

@ -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 |

View File

@ -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;

View File

@ -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));

View File

@ -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>
));

View File

@ -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);
});
});

View File

@ -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;

View File

@ -1,10 +0,0 @@
import React from "react";
const SubInfoPanelHeader = ({ children }) => {
const content = children?.props?.children;
return <>{content}</>;
};
SubInfoPanelHeader.displayName = "SubInfoPanelHeader";
export default SubInfoPanelHeader;

View File

@ -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);

View File

@ -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;

View File

@ -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));

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,9 +0,0 @@
import React from "react";
const SectionWarning = ({ children }) => {
return <div>{children}</div>;
};
SectionWarning.displayName = "SectionWarning";
export default SectionWarning;

View File

@ -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,

View File

@ -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 = () => {

View File

@ -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[];

View File

@ -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) {

View 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;
};

View File

@ -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) {

View 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;
};

View File

@ -1,4 +1,4 @@
export const enum AvatarRole {
export enum AvatarRole {
owner = "owner",
admin = "admin",
guest = "guest",

View File

@ -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

View File

@ -16,6 +16,7 @@ export type TOption = {
description?: string;
quota?: "free" | "paid";
isSeparator?: boolean;
isSelected?: boolean;
};
export interface ComboboxProps {

View File

@ -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 };

View 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;
}

View File

@ -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;

View 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);

View File

@ -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 />}

View File

@ -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}
/>
)}
</>

View 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);

View File

@ -5,7 +5,7 @@ Default section
### Usage
```js
import Section from "@docspace/common/components/Section";
import Section from "@docspace/shared/components/section";
```
```jsx

View File

@ -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 };

View File

@ -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 */

View File

@ -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();

View File

@ -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}

View File

@ -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;

View File

@ -1 +1,3 @@
export { Selector } from "./Selector";
export type { SelectorProps, TSelectorItem } from "./Selector.types";

View File

@ -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 (

View File

@ -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;
}

View File

@ -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];
}

View 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;
}

View 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;

View File

@ -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;

View 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;
};

View 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;
};

View File

@ -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;

View File

@ -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%;

View File

@ -0,0 +1,7 @@
import { RectangleSkeletonProps } from "../../rectangle";
export interface FilterLoaderProps extends RectangleSkeletonProps {
id?: string;
className?: string;
style?: React.CSSProperties;
}

View File

@ -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;

View File

@ -0,0 +1,3 @@
import FilterLoader from "./FilterLoader";
export { FilterLoader };

View File

@ -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 {

View File

@ -0,0 +1,8 @@
import { RectangleSkeletonProps } from "../../rectangle";
export interface FilterBlockProps extends RectangleSkeletonProps {
id?: string;
className?: string;
isRooms?: boolean;
isAccounts?: boolean;
}

View 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;

View File

@ -0,0 +1,4 @@
import { FilterLoader } from "./Filter";
import FilterBlockLoader from "./FilterBlock";
export { FilterBlockLoader, FilterLoader };

View File

@ -12,4 +12,5 @@ export interface RectangleSkeletonProps {
foregroundOpacity?: number;
speed?: number;
animate?: boolean;
style?: React.CSSProperties;
}

View File

@ -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;

View 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;

View File

@ -0,0 +1,4 @@
import RowLoader from "./Row";
import SearchLoader from "./Search";
export { SearchLoader, RowLoader };

View File

@ -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