Merge pull request #176 from ONLYOFFICE/feature/shared-navigation

Feature/shared navigation
This commit is contained in:
Alexey Safronov 2024-01-15 18:09:39 +04:00 committed by GitHub
commit 2acbd2f4fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1523 additions and 1358 deletions

View File

@ -40,7 +40,7 @@ import copy from "copy-to-clipboard";
import { useNavigate, useLocation } from "react-router-dom";
import Loaders from "@docspace/common/components/Loaders";
import Navigation from "@docspace/common/components/Navigation";
import Navigation from "@docspace/shared/components/Navigation";
import FilesFilter from "@docspace/shared/api/files/filter";
import { resendInvitesAgain } from "@docspace/shared/api/people";

View File

@ -1,7 +1,7 @@
import React from "react";
import { useTranslation } from "react-i18next";
import TrashWarning from "@docspace/common/components/Navigation/sub-components/trash-warning";
import TrashWarning from "@docspace/shared/components/Navigation/sub-components/TrashWarning";
const Warning = () => {
const { t } = useTranslation("Files");

View File

@ -1,202 +0,0 @@
import styled, { css } from "styled-components";
import { tablet, mobile } from "@docspace/shared/utils";
const StyledContainer = styled.div`
${(props) =>
!props.isDropBoxComponent &&
props.isDesktop &&
css`
width: fit-content;
max-width: ${props.isInfoPanelVisible
? `calc(100%)`
: `calc(100% - 72px)`};
`}
display: grid;
align-items: center;
margin-right: ${(props) => (props.isTrashFolder ? "16px" : 0)};
grid-template-columns: ${({ isRootFolder, withLogo }) =>
isRootFolder
? withLogo
? "1fr auto 1fr"
: "auto 1fr"
: withLogo
? "1fr 49px auto 1fr"
: "49px auto 1fr"};
.navigation-logo {
display: flex;
height: 24px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`}
@media ${tablet} {
.logo-icon_svg {
display: none;
}
}
.header_separator {
display: ${({ isRootFolder }) => (isRootFolder ? "block" : "none")};
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
border-right: 1px solid #dfe2e3;
margin: 0 15px 0 0;
`
: css`
border-left: 1px solid #dfe2e3;
margin: 0 0 0 15px;
`}
height: 21px;
}
.header-burger {
cursor: pointer;
display: none;
margin-top: -2px;
img {
height: 28px;
width: 28px;
}
@media ${tablet} {
display: flex;
}
@media ${mobile} {
display: none;
}
}
}
.drop-box-logo {
display: none;
@media ${tablet} {
display: grid;
}
}
height: 100%;
${(props) =>
props.isDesktopClient &&
props.isDropBoxComponent &&
css`
max-height: 32px;
`}
.navigation-arrow-container {
display: flex;
}
.arrow-button {
padding-top: 2px;
width: 17px;
min-width: 17px;
svg {
${({ theme }) =>
theme.interfaceDirection === "rtl" && `transform: scaleX(-1);`}
}
}
.title-container {
display: grid;
grid-template-columns: minmax(1px, max-content) auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.room-title {
cursor: pointer;
}
}
.navigation-header-separator {
display: block;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 16px;
border-left: ${(props) =>
`1px solid ${props.theme.navigation.icon.stroke}`};
`
: css`
padding-left: 16px;
border-right: ${(props) =>
`1px solid ${props.theme.navigation.icon.stroke}`};
`}
height: 21px;
@media ${mobile} {
display: none;
}
}
.headline-heading {
display: flex;
height: 32px;
align-items: center;
}
.title-block {
display: flex;
align-items: center;
flex-direction: row;
position: relative;
cursor: pointer;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
gap: 8px;
.title-icon {
min-width: 17px;
min-height: 17px;
width: 17px;
height: 17px;
svg {
path,
rect {
fill: ${({ theme }) => theme.navigation.publicIcon};
}
}
}
}
@media ${tablet} {
width: 100%;
grid-template-columns: ${({ isRootFolder, withLogo }) =>
isRootFolder
? withLogo
? "59px 1fr auto"
: "1fr auto"
: withLogo
? "43px 49px 1fr auto"
: "49px 1fr auto"};
}
@media ${mobile} {
.navigation-logo {
display: none;
}
grid-template-columns: ${(props) =>
props.isRootFolder ? "1fr auto" : "29px 1fr auto"};
}
`;
export default StyledContainer;

View File

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

View File

@ -1,27 +0,0 @@
import React from "react";
import { IconButton } from "@docspace/shared/components/icon-button";
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
const ArrowButton = ({ isRootFolder, onBackToParentFolder }) => {
return (
<>
{!isRootFolder ? (
<div className="navigation-arrow-container">
<IconButton
iconName={ArrowPathReactSvgUrl}
size="17"
isFill={true}
onClick={onBackToParentFolder}
className="arrow-button"
/>
<div className="navigation-header-separator" />
</div>
) : (
<></>
)}
</>
);
};
export default React.memo(ArrowButton);

View File

@ -1,259 +0,0 @@
import React from "react";
import styled, { css } from "styled-components";
import PropTypes from "prop-types";
import { tablet } from "@docspace/shared/utils";
import { Base } from "@docspace/shared/themes";
import ToggleInfoPanelButton from "./toggle-infopanel-btn";
import PlusButton from "./plus-btn";
import ContextButton from "./context-btn";
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/icons/17/vertical-dots.react.svg?url";
const StyledContainer = styled.div`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 16px;
`
: css`
margin-left: 16px;
`}
display: flex;
align-items: center;
height: 32px;
.add-button {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`}
min-width: 15px;
@media ${tablet} {
display: ${(props) => (props.isFrame ? "flex" : "none")};
}
}
.add-drop-down {
margin-top: 8px;
}
.option-button {
min-width: 17px;
/* ${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`} */
/* @media ${tablet} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 9px;
`
: css`
margin-right: 9px;
`}
} */
}
.trash-button {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`}
min-width: 15px;
}
`;
const StyledInfoPanelToggleWrapper = styled.div`
display: flex;
align-items: center;
align-self: center;
justify-content: center;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
`
: css`
margin-left: auto;
`}
@media ${tablet} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: ${(props) => (props.isRootFolder ? "auto" : "0")};
`
: css`
margin-left: ${(props) => (props.isRootFolder ? "auto" : "0")};
`}
}
.info-panel-toggle-bg {
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: ${(props) =>
props.isInfoPanelVisible
? props.theme.infoPanel.sectionHeaderToggleBgActive
: props.theme.infoPanel.sectionHeaderToggleBg};
path {
fill: ${(props) =>
props.isInfoPanelVisible
? props.theme.infoPanel.sectionHeaderToggleIconActive
: props.theme.infoPanel.sectionHeaderToggleIcon};
}
}
`;
StyledInfoPanelToggleWrapper.defaultProps = { theme: Base };
const ControlButtons = ({
personal,
isDropBoxComponent,
isRootFolder,
canCreate,
getContextOptionsFolder,
getContextOptionsPlus,
isEmptyFilesList,
clearTrash,
isInfoPanelVisible,
toggleInfoPanel,
toggleDropBox,
isDesktop,
titles,
withMenu,
onPlusClick,
isFrame,
isPublicRoom,
isTrashFolder,
isMobile,
}) => {
const toggleInfoPanelAction = () => {
toggleInfoPanel && toggleInfoPanel();
toggleDropBox && toggleDropBox();
};
return (
<StyledContainer isDropBoxComponent={isDropBoxComponent} isFrame={isFrame}>
{!isRootFolder || (isTrashFolder && !isEmptyFilesList) ? (
<>
{!isMobile && canCreate && (
<PlusButton
className="add-button"
getData={getContextOptionsPlus}
withMenu={withMenu}
onPlusClick={onPlusClick}
isFrame={isFrame}
title={titles?.actions}
/>
)}
{/* <ContextMenuButton
id="header_optional-button"
zIndex={402}
className="option-button"
directionX="right"
iconName={VerticalDotsReactSvgUrl}
size={15}
isFill
getData={getContextOptionsFolder}
isDisabled={false}
title={titles?.contextMenu}
/> */}
<ContextButton
id="header_optional-button"
className="option-button"
getData={getContextOptionsFolder}
withMenu={withMenu}
//onPlusClick={onPlusClick}
title={titles?.actions}
isTrashFolder={isTrashFolder}
isMobile={isMobile}
/>
{!isDesktop && (
<ToggleInfoPanelButton
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
toggleInfoPanel={toggleInfoPanelAction}
/>
)}
</>
) : canCreate ? (
<>
{!isMobile && (
<PlusButton
id="header_add-button"
className="add-button"
getData={getContextOptionsPlus}
withMenu={withMenu}
onPlusClick={onPlusClick}
isFrame={isFrame}
title={titles?.actions}
/>
)}
{!isDesktop && (
<ToggleInfoPanelButton
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
toggleInfoPanel={toggleInfoPanelAction}
/>
)}
</>
) : (
<>
{!isDesktop && (
<ToggleInfoPanelButton
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
toggleInfoPanel={toggleInfoPanelAction}
/>
)}
{isPublicRoom && (
<ContextButton
id="header_optional-button"
className="option-button"
getData={getContextOptionsFolder}
withMenu={withMenu}
title={titles?.contextMenu}
isTrashFolder={isTrashFolder}
isMobile={isMobile}
/>
)}
</>
)}
</StyledContainer>
);
};
ControlButtons.propTypes = {
personal: PropTypes.bool,
isRootFolder: PropTypes.bool,
canCreate: PropTypes.bool,
getContextOptionsFolder: PropTypes.func,
getContextOptionsPlus: PropTypes.func,
};
export default React.memo(ControlButtons);

View File

@ -1,246 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import styled, { css, useTheme } from "styled-components";
import { VariableSizeList } from "react-window";
import { CustomScrollbarsVirtualList } from "@docspace/shared/components/scrollbar";
import ArrowButton from "./arrow-btn";
import Text from "./text";
import ControlButtons from "./control-btn";
import Item from "./item";
import StyledContainer from "../StyledNavigation";
import NavigationLogo from "./logo-block";
import { tablet, mobile } from "@docspace/shared/utils";
import { ReactSVG } from "react-svg";
import { Base } from "@docspace/shared/themes";
import { DeviceType } from "@docspace/shared/enums";
const StyledBox = styled.div`
position: absolute;
top: 0px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
right: -20px;
${props.withLogo && `right: 207px;`};
`
: css`
left: -20px;
${props.withLogo && `left: 207px;`};
`}
padding: 0 20px;
padding-top: 18px;
width: unset;
height: ${(props) => (props.height ? `${props.height}px` : "fit-content")};
max-height: calc(100vh - 48px);
z-index: 401;
display: table;
margin: auto;
flex-direction: column;
background: ${(props) => props.theme.navigation.background};
box-shadow: ${(props) => props.theme.navigation.boxShadow};
border-radius: 0px 0px 6px 6px;
.title-container {
display: grid;
grid-template-columns: minmax(1px, max-content) auto;
}
@media ${tablet} {
width: ${({ dropBoxWidth }) => dropBoxWidth + "px"};
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
right: -16px;
`
: css`
left: -16px;
`}
padding: 0 16px;
padding-top: 14px;
}
@media ${mobile} {
width: ${({ dropBoxWidth }) => dropBoxWidth + "px"};
padding-top: 10px !important;
}
`;
StyledBox.defaultProps = { theme: Base };
const Row = React.memo(({ data, index, style }) => {
const isRoot = index === data[0].length - 1;
return (
<Item
key={data[0][index].id}
id={data[0][index].id}
title={data[0][index].title}
isRootRoom={data[0][index].isRootRoom}
isRoot={isRoot}
onClick={data[1]}
withLogo={data[2].withLogo}
currentDeviceType={data[2].currentDeviceType}
style={{ ...style }}
/>
);
});
const DropBox = React.forwardRef(
(
{
sectionHeight,
showText,
dropBoxWidth,
isRootFolder,
onBackToParentFolder,
title,
personal,
canCreate,
navigationItems,
getContextOptionsFolder,
getContextOptionsPlus,
toggleDropBox,
toggleInfoPanel,
onClickAvailable,
isInfoPanelVisible,
maxHeight,
isOpen,
isDesktop,
isDesktopClient,
showRootFolderNavigation,
withLogo,
burgerLogo,
titleIcon,
currentDeviceType,
navigationTitleContainerNode,
},
ref
) => {
const [dropBoxHeight, setDropBoxHeight] = React.useState(0);
const countItems = navigationItems.length;
const getItemSize = (index) => {
if (index === countItems - 1) return 51;
return currentDeviceType !== DeviceType.desktop ? 36 : 30;
};
const { interfaceDirection } = useTheme();
React.useEffect(() => {
const itemsHeight = navigationItems.map((item, index) =>
getItemSize(index)
);
const currentHeight = itemsHeight.reduce((a, b) => a + b);
let navHeight = 41;
if (currentDeviceType === DeviceType.tablet) {
navHeight = 49;
}
if (currentDeviceType === DeviceType.mobile) {
navHeight = 45;
}
setDropBoxHeight(
currentHeight + navHeight > sectionHeight
? sectionHeight - navHeight - 20
: currentHeight
);
}, [sectionHeight, currentDeviceType]);
const isTabletView = currentDeviceType === DeviceType.tablet;
return (
<>
<StyledBox
ref={ref}
maxHeight={maxHeight}
height={sectionHeight < dropBoxHeight ? sectionHeight : null}
showText={showText}
dropBoxWidth={dropBoxWidth}
isDesktop={isDesktop}
withLogo={withLogo}
>
<StyledContainer
canCreate={canCreate}
isDropBoxComponent={true}
isInfoPanelVisible={isInfoPanelVisible}
isDesktopClient={isDesktopClient}
withLogo={!!withLogo && isTabletView}
>
{withLogo && (
<NavigationLogo
logo={withLogo}
burgerLogo={burgerLogo}
className="navigation-logo drop-box-logo"
/>
)}
<ArrowButton
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolder}
/>
{navigationTitleContainerNode}
<ControlButtons
isDesktop={isDesktop}
isMobile={currentDeviceType !== DeviceType.desktop}
personal={personal}
isRootFolder={isRootFolder}
isDropBoxComponent={true}
canCreate={canCreate}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
toggleInfoPanel={toggleInfoPanel}
toggleDropBox={toggleDropBox}
isInfoPanelVisible={isInfoPanelVisible}
/>
</StyledContainer>
<VariableSizeList
direction={interfaceDirection}
height={dropBoxHeight}
width={"auto"}
itemCount={countItems}
itemSize={getItemSize}
itemData={[
navigationItems,
onClickAvailable,
{ withLogo: !!withLogo, currentDeviceType },
]}
outerElementType={CustomScrollbarsVirtualList}
>
{Row}
</VariableSizeList>
</StyledBox>
</>
);
}
);
DropBox.propTypes = {
width: PropTypes.number,
changeWidth: PropTypes.bool,
isRootFolder: PropTypes.bool,
onBackToParentFolder: PropTypes.func,
title: PropTypes.string,
personal: PropTypes.bool,
canCreate: PropTypes.bool,
navigationItems: PropTypes.arrayOf(PropTypes.object),
getContextOptionsFolder: PropTypes.func,
getContextOptionsPlus: PropTypes.func,
toggleDropBox: PropTypes.func,
onClickAvailable: PropTypes.func,
};
export default React.memo(DropBox);

View File

@ -1,126 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import styled, { css } from "styled-components";
import { Text } from "@docspace/shared/components/text";
import DefaultIcon from "PUBLIC_DIR/images/default.react.svg";
import RootIcon from "PUBLIC_DIR/images/root.react.svg";
import DefaultTabletIcon from "PUBLIC_DIR/images/default.tablet.react.svg";
import RootTabletIcon from "PUBLIC_DIR/images/root.tablet.react.svg";
import { tablet, mobile } from "@docspace/shared/utils";
import { Base } from "@docspace/shared/themes";
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
import { DeviceType } from "@docspace/shared/enums";
const StyledItem = styled.div`
height: auto;
width: auto !important;
position: relative;
display: grid;
align-items: ${(props) => (props.isRoot ? "baseline" : "end")};
grid-template-columns: 17px auto;
cursor: pointer;
${({ theme }) =>
theme.interfaceDirection === "rtl" ? `margin-right: 0;` : `margin-left: 0;`}
@media ${tablet} {
${({ withLogo }) =>
withLogo &&
css`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 44px;
`
: css`
margin-left: 44px;
`}
`};
}
@media ${mobile} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 0;
`
: css`
margin-left: 0;
`}
}
`;
const StyledText = styled(Text)`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 10px;
`
: css`
margin-left: 10px;
`}
position: relative;
bottom: ${(props) => (props.isRoot ? "2px" : "-1px")};
`;
const Item = ({
id,
title,
isRoot,
isRootRoom,
onClick,
withLogo,
currentDeviceType,
...rest
}) => {
const onClickAvailable = () => {
onClick && onClick(id, isRootRoom);
};
return (
<StyledItem
id={id}
isRoot={isRoot}
onClick={onClickAvailable}
withLogo={withLogo}
{...rest}
>
<ColorTheme isRoot={isRoot} themeId={ThemeId.IconWrapper}>
{currentDeviceType !== DeviceType.desktop ? (
isRoot ? (
<RootTabletIcon />
) : (
<DefaultTabletIcon />
)
) : isRoot ? (
<RootIcon />
) : (
<DefaultIcon />
)}
</ColorTheme>
<StyledText
isRoot={isRoot}
fontWeight={isRoot ? "600" : "400"}
fontSize={"15px"}
truncate={true}
title={title}
>
{title}
</StyledText>
</StyledItem>
);
};
Item.propTypes = {
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
title: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
isRoot: PropTypes.bool,
onClick: PropTypes.func,
};
export default React.memo(Item);

View File

@ -1,15 +0,0 @@
import React from "react";
const NavigationLogo = ({ logo, burgerLogo, ...rest }) => {
return (
<div {...rest}>
<img className="logo-icon_svg" src={logo} />
<div className="header-burger">
<img src={burgerLogo} /* onClick={onLogoClick} */ />
</div>
<div className="header_separator" />
</div>
);
};
export default NavigationLogo;

View File

@ -1,65 +0,0 @@
import React, { useState, useRef } from "react";
import PropTypes from "prop-types";
import PlusReactSvgUrl from "PUBLIC_DIR/images/icons/17/plus.svg?url";
import { IconButton } from "@docspace/shared/components/icon-button";
import { ContextMenu } from "@docspace/shared/components/context-menu";
const PlusButton = (props) => {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
const menuRef = useRef(null);
const { className, getData, withMenu, onPlusClick, isFrame, ...rest } = props;
const toggle = (e, isOpen) => {
isOpen ? menuRef.current.show(e) : menuRef.current.hide(e);
setIsOpen(isOpen);
};
const onClick = (e) => {
if (withMenu) toggle(e, !isOpen);
else onPlusClick && onPlusClick();
};
const onHide = () => {
setIsOpen(false);
};
const model = getData();
return (
<div ref={ref} className={className} {...rest}>
<IconButton
onClick={onClick}
iconName={PlusReactSvgUrl}
id={props.id}
size={17}
isFill
/>
<ContextMenu
model={model}
containerRef={ref}
ref={menuRef}
onHide={onHide}
scaled={false}
directionX="right"
leftOffset={isFrame ? 190 : 150}
/>
</div>
);
};
PlusButton.propTypes = {
className: PropTypes.string,
getData: PropTypes.func.isRequired,
onPlusClick: PropTypes.func,
id: PropTypes.string,
};
PlusButton.defaultProps = {
withMenu: true,
};
export default PlusButton;

View File

@ -1,169 +0,0 @@
import React from "react";
import styled, { css } from "styled-components";
import PropTypes from "prop-types";
import ExpanderDownIcon from "PUBLIC_DIR/images/expander-down.react.svg";
import ArrowIcon from "PUBLIC_DIR/images/arrow.react.svg";
import { Heading } from "@docspace/shared/components/heading";
import { tablet, mobile, commonIconsStyles } from "@docspace/shared/utils";
import { Base } from "@docspace/shared/themes";
const StyledTextContainer = styled.div`
display: flex;
align-items: center;
flex-direction: row;
position: relative;
${(props) =>
!props.isRootFolder && !props.isRootFolderTitle && "cursor: pointer"};
${(props) =>
props.isRootFolderTitle &&
(props.theme.interfaceDirection === "rtl"
? "padding-left: 3px;"
: "padding-right: 3px;")};
${(props) =>
!props.isRootFolderTitle &&
css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`};
`;
const StyledHeading = styled(Heading)`
font-weight: 700;
font-size: ${(props) => props.theme.getCorrectFontSize("18px")};
line-height: 24px;
margin: 0;
${(props) =>
props.isRootFolderTitle &&
`color: ${props.theme.navigation.rootFolderTitleColor}`};
@media ${tablet} {
font-size: ${(props) => props.theme.getCorrectFontSize("21px")};
line-height: 28px;
}
@media ${mobile} {
font-size: ${(props) => props.theme.getCorrectFontSize("18px")};
line-height: 24px;
}
`;
const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
min-width: 8px !important;
width: 8px !important;
min-height: 18px !important;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 4px 0 2px;
`
: css`
padding: 0 2px 0 4px;
`}
path {
fill: ${(props) => props.theme.navigation.expanderColor};
}
${commonIconsStyles};
`;
const StyledArrowIcon = styled(ArrowIcon)`
height: 12px;
min-width: 12px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 6px;
`
: css`
padding-left: 6px;
`}
path {
fill: ${(props) => props.theme.navigation.rootFolderTitleColor};
}
`;
StyledExpanderDownIcon.defaultProps = { theme: Base };
const StyledExpanderDownIconRotate = styled(ExpanderDownIcon)`
min-width: 8px !important;
width: 8px !important;
min-height: 18px !important;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 2px 0 4px;
`
: css`
padding: 0 4px 0 2px;
`}
transform: rotate(-180deg);
path {
fill: ${(props) => props.theme.navigation.expanderColor};
}
${commonIconsStyles};
`;
StyledExpanderDownIconRotate.defaultProps = { theme: Base };
const Text = ({
title,
isRootFolder,
isOpen,
isRootFolderTitle,
onClick,
...rest
}) => {
return (
<StyledTextContainer
isRootFolder={isRootFolder}
onClick={onClick}
isOpen={isOpen}
isRootFolderTitle={isRootFolderTitle}
{...rest}
>
<StyledHeading
type="content"
title={title}
truncate={true}
isRootFolderTitle={isRootFolderTitle}
>
{title}
</StyledHeading>
{isRootFolderTitle && <StyledArrowIcon />}
{!isRootFolderTitle && !isRootFolder ? (
isOpen ? (
<StyledExpanderDownIconRotate />
) : (
<StyledExpanderDownIcon />
)
) : (
<></>
)}
</StyledTextContainer>
);
};
Text.propTypes = {
title: PropTypes.string,
isOpen: PropTypes.bool,
isRootFolder: PropTypes.bool,
onCLick: PropTypes.func,
};
export default React.memo(Text);

View File

@ -1,87 +0,0 @@
import React from "react";
import styled, { css } from "styled-components";
import PanelReactSvgUrl from "PUBLIC_DIR/images/panel.react.svg?url";
import { IconButton } from "@docspace/shared/components/icon-button";
import { tablet } from "@docspace/shared/utils";
import { Base } from "@docspace/shared/themes";
import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme";
const StyledInfoPanelToggleColorThemeWrapper = styled(ColorTheme)`
align-self: center;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
transform: scaleX(-1);
`
: css`
margin-left: auto;
`}
margin-bottom: 1px;
padding: 0;
.info-panel-toggle {
margin-inline-end: 8px;
}
${(props) =>
props.isInfoPanelVisible &&
css`
.info-panel-toggle-bg {
height: 30px;
width: 30px;
background: ${props.theme.backgroundAndSubstrateColor};
border: 1px solid ${props.theme.backgroundAndSubstrateColor};
border-radius: 50%;
.info-panel-toggle {
margin: auto;
margin-top: 25%;
}
}
`}
@media ${tablet} {
display: none;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: ${props.isRootFolder ? "auto" : "0"};
`
: css`
margin-left: ${props.isRootFolder ? "auto" : "0"};
`}
}
`;
StyledInfoPanelToggleColorThemeWrapper.defaultProps = { theme: Base };
const ToggleInfoPanelButton = ({
isRootFolder,
isInfoPanelVisible,
toggleInfoPanel,
id,
titles,
}) => {
return (
<StyledInfoPanelToggleColorThemeWrapper
isRootFolder={isRootFolder}
themeId={ThemeId.InfoPanelToggle}
isInfoPanelVisible={isInfoPanelVisible}
>
<div className="info-panel-toggle-bg">
<IconButton
id={id}
className="info-panel-toggle"
iconName={PanelReactSvgUrl}
size="16"
isFill={true}
title={titles?.infoPanel}
onClick={toggleInfoPanel}
/>
</div>
</StyledInfoPanelToggleColorThemeWrapper>
);
};
export default ToggleInfoPanelButton;

View File

@ -1,35 +0,0 @@
import React from "react";
import styled, { css } from "styled-components";
import { tablet } from "@docspace/shared/utils";
const StyledTrashWarning = styled.div`
box-sizing: border-box;
height: 32px;
padding: 8px 12px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: ${({ theme }) =>
theme.interfaceDirection === "rtl" ? `right` : `left`};
font-weight: 400;
font-size: ${(props) => props.theme.getCorrectFontSize("12px")};
line-height: 16px;
color: ${({ theme }) => theme.section.header.trashErasureLabelText};
background: ${({ theme }) =>
theme.section.header.trashErasureLabelBackground};
@media ${tablet} {
margin-bottom: 16px;
}
`;
const TrashWarning = ({ title }) => {
return (
<StyledTrashWarning className="trash-warning">{title}</StyledTrashWarning>
);
};
export default TrashWarning;

View File

@ -52,6 +52,8 @@ export type HeaderType = {
export type ContextMenuModel = ContextMenuType | SeparatorType;
export type TGetContextMenuModel = () => ContextMenuModel[];
export interface ContextMenuProps {
/** Unique identifier of the element */
id?: string;
@ -98,7 +100,7 @@ export interface ContextMenuProps {
/** Fills the icons with default colors */
fillIcon?: boolean;
/** Function that returns an object containing the elements of the context menu */
getContextModel?: () => ContextMenuModel[];
getContextModel?: TGetContextMenuModel;
/** Specifies the offset */
leftOffset?: number;
rightOffset?: number;
@ -106,3 +108,8 @@ export interface ContextMenuProps {
isArchive?: boolean;
ref?: React.RefObject<HTMLDivElement>;
}
export type TContextMenuRef = {
show: (e: React.MouseEvent) => void;
hide: (e: React.MouseEvent) => {};
};

View File

@ -2,8 +2,16 @@ import {
ContextMenuModel,
ContextMenuType,
SeparatorType,
TGetContextMenuModel,
TContextMenuRef,
} from "./ContextMenu.types";
export type { ContextMenuModel, ContextMenuType, SeparatorType };
export type {
TContextMenuRef,
TGetContextMenuModel,
ContextMenuModel,
ContextMenuType,
SeparatorType,
};
export { ContextMenu } from "./ContextMenu";

View File

@ -0,0 +1,657 @@
import styled, { css } from "styled-components";
import ExpanderDownIcon from "PUBLIC_DIR/images/expander-down.react.svg";
import ArrowIcon from "PUBLIC_DIR/images/arrow.react.svg";
import { tablet, mobile, commonIconsStyles } from "../../utils";
import { Base } from "../../themes";
import { ColorTheme } from "../color-theme";
import { Heading } from "../heading";
import { Text } from "../text";
const StyledContainer = styled.div<{
isDropBoxComponent?: boolean;
isDesktop: boolean;
isInfoPanelVisible: boolean;
isTrashFolder?: boolean;
isRootFolder?: boolean;
withLogo: boolean;
isDesktopClient?: boolean;
width?: number;
}>`
${(props) =>
!props.isDropBoxComponent &&
props.isDesktop &&
css`
width: fit-content;
max-width: ${props.isInfoPanelVisible
? `calc(100%)`
: `calc(100% - 72px)`};
`}
display: grid;
align-items: center;
margin-right: ${(props) => (props.isTrashFolder ? "16px" : 0)};
grid-template-columns: ${({ isRootFolder, withLogo }) =>
isRootFolder
? withLogo
? "1fr auto 1fr"
: "auto 1fr"
: withLogo
? "1fr 49px auto 1fr"
: "49px auto 1fr"};
.navigation-logo {
display: flex;
height: 24px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`}
@media ${tablet} {
.logo-icon_svg {
display: none;
}
}
.header_separator {
display: ${({ isRootFolder }) => (isRootFolder ? "block" : "none")};
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
border-right: 1px solid #dfe2e3;
margin: 0 15px 0 0;
`
: css`
border-left: 1px solid #dfe2e3;
margin: 0 0 0 15px;
`}
height: 21px;
}
.header-burger {
cursor: pointer;
display: none;
margin-top: -2px;
img {
height: 28px;
width: 28px;
}
@media ${tablet} {
display: flex;
}
@media ${mobile} {
display: none;
}
}
}
.drop-box-logo {
display: none;
@media ${tablet} {
display: grid;
}
}
height: 100%;
${(props) =>
props.isDesktopClient &&
props.isDropBoxComponent &&
css`
max-height: 32px;
`}
.navigation-arrow-container {
display: flex;
}
.arrow-button {
padding-top: 2px;
width: 17px;
min-width: 17px;
svg {
${({ theme }) =>
theme.interfaceDirection === "rtl" && `transform: scaleX(-1);`}
}
}
.title-container {
display: grid;
grid-template-columns: minmax(1px, max-content) auto;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.room-title {
cursor: pointer;
}
}
.navigation-header-separator {
display: block;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 16px;
border-left: ${`1px solid ${props.theme.navigation.icon.stroke}`};
`
: css`
padding-left: 16px;
border-right: ${`1px solid ${props.theme.navigation.icon.stroke}`};
`}
height: 21px;
@media ${mobile} {
display: none;
}
}
.headline-heading {
display: flex;
height: 32px;
align-items: center;
}
.title-block {
display: flex;
align-items: center;
flex-direction: row;
position: relative;
cursor: pointer;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
gap: 8px;
.title-icon {
min-width: 17px;
min-height: 17px;
width: 17px;
height: 17px;
svg {
path,
rect {
fill: ${({ theme }) => theme.navigation.publicIcon};
}
}
}
}
@media ${tablet} {
width: 100%;
grid-template-columns: ${({ isRootFolder, withLogo }) =>
isRootFolder
? withLogo
? "59px 1fr auto"
: "1fr auto"
: withLogo
? "43px 49px 1fr auto"
: "49px 1fr auto"};
}
@media ${mobile} {
.navigation-logo {
display: none;
}
grid-template-columns: ${(props) =>
props.isRootFolder ? "1fr auto" : "29px 1fr auto"};
}
`;
const StyledInfoPanelToggleColorThemeWrapper = styled(ColorTheme)<{
isInfoPanelVisible?: boolean;
isRootFolder?: boolean;
}>`
align-self: center;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
transform: scaleX(-1);
`
: css`
margin-left: auto;
`}
margin-bottom: 1px;
padding: 0;
.info-panel-toggle {
margin-inline-end: 8px;
}
${(props) =>
props.isInfoPanelVisible &&
css`
.info-panel-toggle-bg {
height: 30px;
width: 30px;
background: ${props.theme.backgroundAndSubstrateColor};
border: 1px solid ${props.theme.backgroundAndSubstrateColor};
border-radius: 50%;
.info-panel-toggle {
margin: auto;
margin-top: 25%;
}
}
`}
@media ${tablet} {
display: none;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: ${props.isRootFolder ? "auto" : "0"};
`
: css`
margin-left: ${props.isRootFolder ? "auto" : "0"};
`}
}
`;
StyledInfoPanelToggleColorThemeWrapper.defaultProps = { theme: Base };
const StyledControlButtonContainer = styled.div<{ isFrame?: boolean }>`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 16px;
`
: css`
margin-left: 16px;
`}
display: flex;
align-items: center;
height: 32px;
.add-button {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`}
min-width: 15px;
@media ${tablet} {
display: ${(props) => (props.isFrame ? "flex" : "none")};
}
}
.add-drop-down {
margin-top: 8px;
}
.option-button {
min-width: 17px;
/* ${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`} */
/* @media ${tablet} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 9px;
`
: css`
margin-right: 9px;
`}
} */
}
.trash-button {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-left: 16px;
`
: css`
margin-right: 16px;
`}
min-width: 15px;
}
`;
const StyledInfoPanelToggleWrapper = styled.div<{
isRootFolder: boolean;
isInfoPanelVisible: boolean;
}>`
display: flex;
align-items: center;
align-self: center;
justify-content: center;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: auto;
`
: css`
margin-left: auto;
`}
@media ${tablet} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: ${props.isRootFolder ? "auto" : "0"};
`
: css`
margin-left: ${props.isRootFolder ? "auto" : "0"};
`}
}
.info-panel-toggle-bg {
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: ${(props) =>
props.isInfoPanelVisible
? props.theme.infoPanel.sectionHeaderToggleBgActive
: props.theme.infoPanel.sectionHeaderToggleBg};
path {
fill: ${(props) =>
props.isInfoPanelVisible
? props.theme.infoPanel.sectionHeaderToggleIconActive
: props.theme.infoPanel.sectionHeaderToggleIcon};
}
}
`;
StyledInfoPanelToggleWrapper.defaultProps = { theme: Base };
const StyledTrashWarning = styled.div`
box-sizing: border-box;
height: 32px;
padding: 8px 12px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: ${({ theme }) =>
theme.interfaceDirection === "rtl" ? `right` : `left`};
font-weight: 400;
font-size: ${(props) => props.theme.getCorrectFontSize("12px")};
line-height: 16px;
color: ${({ theme }) => theme.section.header.trashErasureLabelText};
background: ${({ theme }) =>
theme.section.header.trashErasureLabelBackground};
@media ${tablet} {
margin-bottom: 16px;
}
`;
StyledTrashWarning.defaultProps = { theme: Base };
const StyledTextContainer = styled.div<{
isRootFolder: boolean;
isRootFolderTitle: boolean;
}>`
display: flex;
align-items: center;
flex-direction: row;
position: relative;
${(props) =>
!props.isRootFolder && !props.isRootFolderTitle && "cursor: pointer"};
${(props) =>
props.isRootFolderTitle &&
(props.theme.interfaceDirection === "rtl"
? "padding-left: 3px;"
: "padding-right: 3px;")};
${(props) =>
!props.isRootFolderTitle &&
css`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`};
`;
const StyledHeading = styled(Heading)<{ isRootFolderTitle: boolean }>`
font-weight: 700;
font-size: ${(props) => props.theme.getCorrectFontSize("18px")};
line-height: 24px;
margin: 0;
${(props) =>
props.isRootFolderTitle &&
`color: ${props.theme.navigation.rootFolderTitleColor}`};
@media ${tablet} {
font-size: ${(props) => props.theme.getCorrectFontSize("21px")};
line-height: 28px;
}
@media ${mobile} {
font-size: ${(props) => props.theme.getCorrectFontSize("18px")};
line-height: 24px;
}
`;
const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
min-width: 8px !important;
width: 8px !important;
min-height: 18px !important;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 4px 0 2px;
`
: css`
padding: 0 2px 0 4px;
`}
path {
fill: ${(props) => props.theme.navigation.expanderColor};
}
${commonIconsStyles};
`;
const StyledArrowIcon = styled(ArrowIcon)`
height: 12px;
min-width: 12px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding-right: 6px;
`
: css`
padding-left: 6px;
`}
path {
fill: ${(props) => props.theme.navigation.rootFolderTitleColor};
}
`;
StyledExpanderDownIcon.defaultProps = { theme: Base };
const StyledExpanderDownIconRotate = styled(ExpanderDownIcon)`
min-width: 8px !important;
width: 8px !important;
min-height: 18px !important;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
padding: 0 2px 0 4px;
`
: css`
padding: 0 4px 0 2px;
`}
transform: rotate(-180deg);
path {
fill: ${(props) => props.theme.navigation.expanderColor};
}
${commonIconsStyles};
`;
StyledExpanderDownIconRotate.defaultProps = { theme: Base };
const StyledItem = styled.div<{ isRoot: boolean; withLogo: boolean }>`
height: auto;
width: auto !important;
position: relative;
display: grid;
align-items: ${(props) => (props.isRoot ? "baseline" : "end")};
grid-template-columns: 17px auto;
cursor: pointer;
${({ theme }) =>
theme.interfaceDirection === "rtl" ? `margin-right: 0;` : `margin-left: 0;`}
@media ${tablet} {
${({ withLogo }) =>
withLogo &&
css`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 44px;
`
: css`
margin-left: 44px;
`}
`};
}
@media ${mobile} {
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 0;
`
: css`
margin-left: 0;
`}
}
`;
const StyledText = styled(Text)<{ isRoot: boolean }>`
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
margin-right: 10px;
`
: css`
margin-left: 10px;
`}
position: relative;
bottom: ${(props) => (props.isRoot ? "2px" : "-1px")};
`;
const StyledBox = styled.div<{
withLogo: boolean;
height: number | null;
dropBoxWidth: number;
}>`
position: absolute;
top: 0px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
right: -20px;
${props.withLogo && `right: 207px;`};
`
: css`
left: -20px;
${props.withLogo && `left: 207px;`};
`}
padding: 0 20px;
padding-top: 18px;
width: unset;
height: ${(props) => (props.height ? `${props.height}px` : "fit-content")};
max-height: calc(100vh - 48px);
z-index: 401;
display: table;
margin: auto;
flex-direction: column;
background: ${(props) => props.theme.navigation.background};
box-shadow: ${(props) => props.theme.navigation.boxShadow};
border-radius: 0px 0px 6px 6px;
.title-container {
display: grid;
grid-template-columns: minmax(1px, max-content) auto;
}
@media ${tablet} {
width: ${({ dropBoxWidth }) => `${dropBoxWidth}px`};
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
right: -16px;
`
: css`
left: -16px;
`}
padding: 0 16px;
padding-top: 14px;
}
@media ${mobile} {
width: ${({ dropBoxWidth }) => `${dropBoxWidth}px`};
padding-top: 10px !important;
}
`;
StyledBox.defaultProps = { theme: Base };
export {
StyledContainer,
StyledInfoPanelToggleColorThemeWrapper,
StyledControlButtonContainer,
StyledInfoPanelToggleWrapper,
StyledTrashWarning,
StyledTextContainer,
StyledArrowIcon,
StyledExpanderDownIcon,
StyledExpanderDownIconRotate,
StyledHeading,
StyledText,
StyledItem,
StyledBox,
};

View File

@ -1,22 +1,21 @@
import React from "react";
import PropTypes from "prop-types";
import StyledContainer from "./StyledNavigation";
import ArrowButton from "./sub-components/arrow-btn";
import Text from "./sub-components/text";
import ControlButtons from "./sub-components/control-btn";
import DropBox from "./sub-components/drop-box";
import { Consumer, DomHelpers } from "@docspace/shared/utils";
import { Backdrop } from "@docspace/shared/components/backdrop";
import React, { useCallback } from "react";
import { ReactSVG } from "react-svg";
import ToggleInfoPanelButton from "./sub-components/toggle-infopanel-btn";
import TrashWarning from "./sub-components/trash-warning";
import NavigationLogo from "./sub-components/logo-block";
import { DeviceType } from "@docspace/shared/enums";
import { Consumer, DomHelpers } from "../../utils";
import { DeviceType } from "../../enums";
import { Backdrop } from "../backdrop";
import ArrowButton from "./sub-components/ArrowBtn";
import Text from "./sub-components/Text";
import ControlButtons from "./sub-components/ControlBtn";
import ToggleInfoPanelButton from "./sub-components/ToggleInfoPanelBtn";
import TrashWarning from "./sub-components/TrashWarning";
import NavigationLogo from "./sub-components/LogoBlock";
import DropBox from "./sub-components/DropBox";
import { StyledContainer } from "./Navigation.styled";
import { INavigationProps } from "./Navigation.types";
const Navigation = ({
tReady,
@ -25,7 +24,7 @@ const Navigation = ({
title,
canCreate,
isTabletView,
personal,
onClickFolder,
navigationItems,
getContextOptionsPlus,
@ -55,61 +54,64 @@ const Navigation = ({
rootRoomTitle,
...rest
}) => {
}: INavigationProps) => {
const [isOpen, setIsOpen] = React.useState(false);
const [firstClick, setFirstClick] = React.useState(true);
const [dropBoxWidth, setDropBoxWidth] = React.useState(0);
const [maxHeight, setMaxHeight] = React.useState(false);
// const [maxHeight, setMaxHeight] = React.useState("");
const dropBoxRef = React.useRef(null);
const containerRef = React.useRef(null);
const dropBoxRef = React.useRef<HTMLDivElement | null>(null);
const containerRef = React.useRef<HTMLDivElement | null>(null);
const isDesktop = currentDeviceType === DeviceType.desktop;
const infoPanelIsVisible = React.useMemo(
() => isDesktop && (!isEmptyPage || (isEmptyPage && isRoom)),
[isDesktop, isEmptyPage, isRoom]
[isDesktop, isEmptyPage, isRoom],
);
const onMissClick = React.useCallback(
(e) => {
e.preventDefault;
const path = e.path || (e.composedPath && e.composedPath());
if (!firstClick) {
!path.includes(dropBoxRef.current) ? toggleDropBox() : null;
} else {
setFirstClick((prev) => !prev);
}
},
[firstClick, toggleDropBox, setFirstClick]
);
const onClickAvailable = React.useCallback(
(id, isRootRoom) => {
onClickFolder && onClickFolder(id, isRootRoom);
toggleDropBox();
},
[onClickFolder, toggleDropBox]
);
const toggleDropBox = () => {
const toggleDropBox = useCallback(() => {
if (navigationItems.length === 0) return;
if (isRootFolder) return setIsOpen(false);
setIsOpen((prev) => !prev);
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
if (containerRef.current)
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
const { top } = DomHelpers.getOffset(containerRef.current);
// const { top } = DomHelpers.getOffset(containerRef.current);
setMaxHeight(`calc(100vh - ${top}px)`);
// setMaxHeight(`calc(100vh - ${top}px)`);
setFirstClick(true);
};
}, [isRootFolder, navigationItems?.length]);
const onMissClick = React.useCallback(
(e: MouseEvent) => {
e.preventDefault();
const path = e.composedPath && e.composedPath();
if (!firstClick) {
if (dropBoxRef.current && !path.includes(dropBoxRef.current))
toggleDropBox();
} else {
setFirstClick((prev) => !prev);
}
},
[firstClick, toggleDropBox, setFirstClick],
);
const onClickAvailable = React.useCallback(
(id: number | string, isRootRoom: boolean) => {
onClickFolder?.(id, isRootRoom);
toggleDropBox();
},
[onClickFolder, toggleDropBox],
);
const onResize = React.useCallback(() => {
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
}, [containerRef.current]);
if (containerRef.current)
setDropBoxWidth(DomHelpers.getOuterWidth(containerRef.current));
}, []);
React.useEffect(() => {
if (isOpen) {
@ -129,7 +131,7 @@ const Navigation = ({
const onBackToParentFolderAction = React.useCallback(() => {
setIsOpen((val) => !val);
onBackToParentFolder && onBackToParentFolder();
onBackToParentFolder?.();
}, [onBackToParentFolder]);
const showRootFolderNavigation =
@ -146,10 +148,16 @@ const Navigation = ({
isOpen={isOpen}
isRootFolder={isRootFolder}
onClick={toggleDropBox}
isRootFolderTitle={false}
/>
</div>
);
const onTextClick = React.useCallback(() => {
onClickFolder(navigationItems[navigationItems.length - 2].id, false);
setIsOpen(false);
}, [navigationItems, onClickFolder]);
const navigationTitleContainerNode = showRootFolderNavigation ? (
<div className="title-container">
<Text
@ -160,15 +168,7 @@ const Navigation = ({
isOpen={isOpen}
isRootFolder={isRootFolder}
isRootFolderTitle
onClick={() => {
{
onClickFolder(
navigationItems[navigationItems.length - 2].id,
false
);
setIsOpen(false);
}
}}
onClick={onTextClick}
/>
{navigationTitleNode}
</div>
@ -185,7 +185,7 @@ const Navigation = ({
<Backdrop
visible={isOpen}
withBackground={false}
withoutBlur={true}
withoutBlur
zIndex={400}
/>
@ -193,14 +193,10 @@ const Navigation = ({
{...rest}
isDesktop={isDesktop}
ref={dropBoxRef}
maxHeight={maxHeight}
dropBoxWidth={dropBoxWidth}
sectionHeight={context.sectionHeight}
showText={showText}
sectionHeight={context.sectionHeight || 0}
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolderAction}
title={title}
personal={personal}
canCreate={canCreate}
navigationItems={navigationItems}
getContextOptionsFolder={getContextOptionsFolder}
@ -210,10 +206,8 @@ const Navigation = ({
isInfoPanelVisible={isInfoPanelVisible}
onClickAvailable={onClickAvailable}
isDesktopClient={isDesktopClient}
showRootFolderNavigation={showRootFolderNavigation}
withLogo={withLogo}
burgerLogo={burgerLogo}
titleIcon={titleIcon}
currentDeviceType={currentDeviceType}
navigationTitleContainerNode={navigationTitleContainerNode}
/>
@ -221,10 +215,8 @@ const Navigation = ({
)}
<StyledContainer
ref={containerRef}
width={context.sectionWidth}
width={context.sectionWidth || 0}
isRootFolder={isRootFolder}
canCreate={canCreate}
isTabletView={isTabletView}
isTrashFolder={isTrashFolder}
isDesktop={isDesktop}
isDesktopClient={isDesktopClient}
@ -235,7 +227,6 @@ const Navigation = ({
{withLogo && (
<NavigationLogo
className="navigation-logo"
logo={withLogo}
burgerLogo={burgerLogo}
/>
)}
@ -247,13 +238,11 @@ const Navigation = ({
{navigationTitleContainerNode}
<ControlButtons
personal={personal}
isRootFolder={isRootFolder}
canCreate={canCreate}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
isEmptyFilesList={isEmptyFilesList}
clearTrash={clearTrash}
toggleInfoPanel={toggleInfoPanel}
isInfoPanelVisible={isInfoPanelVisible}
isDesktop={isDesktop}
@ -266,10 +255,7 @@ const Navigation = ({
/>
</StyledContainer>
{isDesktop && isTrashFolder && !isEmptyPage && (
<TrashWarning
title={titles.trashWarning}
isTabletView={isTabletView}
/>
<TrashWarning title={titles.trashWarning} />
)}
{infoPanelIsVisible && !hideInfoPanel && (
<ToggleInfoPanelButton
@ -286,22 +272,4 @@ const Navigation = ({
);
};
Navigation.propTypes = {
tReady: PropTypes.bool,
isRootFolder: PropTypes.bool,
title: PropTypes.string,
canCreate: PropTypes.bool,
isDesktop: PropTypes.bool,
isTabletView: PropTypes.bool,
personal: PropTypes.bool,
onClickFolder: PropTypes.func,
navigationItems: PropTypes.arrayOf(PropTypes.object),
getContextOptionsPlus: PropTypes.func,
getContextOptionsFolder: PropTypes.func,
onBackToParentFolder: PropTypes.func,
titles: PropTypes.object,
isEmptyPage: PropTypes.bool,
isRoom: PropTypes.bool,
};
export default React.memo(Navigation);

View File

@ -0,0 +1,163 @@
import { DeviceType } from "../../enums";
import { TGetContextMenuModel } from "../context-menu";
export type TOnBackToParenFolder = () => void;
export type TTitles = {
infoPanel?: string;
actions?: string;
contextMenu?: string;
trashWarning?: string;
};
export interface IArrowButtonProps {
isRootFolder: boolean;
onBackToParentFolder: TOnBackToParenFolder;
}
export interface IContextButtonProps {
className: string;
getData: TGetContextMenuModel;
withMenu?: boolean;
isTrashFolder?: boolean;
isMobile: boolean;
id: string;
title?: string;
}
export interface IPlusButtonProps {
className: string;
getData: TGetContextMenuModel;
withMenu?: boolean;
id?: string;
title?: string;
onPlusClick?: () => void;
isFrame?: boolean;
}
export interface IToggleInfoPanelButtonProps {
isRootFolder: boolean;
isInfoPanelVisible: boolean;
toggleInfoPanel: (e: React.MouseEvent) => void;
id?: string;
titles?: TTitles;
}
export interface IControlButtonProps {
isRootFolder: boolean;
canCreate: boolean;
getContextOptionsFolder: TGetContextMenuModel;
getContextOptionsPlus: TGetContextMenuModel;
isEmptyFilesList?: boolean;
isInfoPanelVisible: boolean;
toggleInfoPanel: () => void;
toggleDropBox?: () => void;
isDesktop: boolean;
titles?: TTitles;
withMenu?: boolean;
onPlusClick?: () => void;
isFrame?: boolean;
isPublicRoom?: boolean;
isTrashFolder?: boolean;
isMobile?: boolean;
}
export interface ITextProps {
title: string;
isOpen: boolean;
isRootFolder: boolean;
isRootFolderTitle: boolean;
onClick: () => void;
className?: string;
}
export interface INavigationLogoProps {
logo?: string;
burgerLogo: string;
className: string;
}
export type TOnNavigationItemClick = (
id: string | number,
isRootRoom: boolean,
) => void;
export interface INavigationItemProps {
id: string | number;
title: string;
isRoot: boolean;
isRootRoom: boolean;
onClick: TOnNavigationItemClick;
withLogo: boolean;
currentDeviceType: DeviceType;
style?: React.CSSProperties;
}
export type TNavigationItem = {
id: string | number;
title: string;
isRootRoom: boolean;
};
export type TRowData = [
TNavigationItem[],
TOnNavigationItemClick,
{ withLogo: boolean; currentDeviceType: DeviceType },
];
export interface IDropBoxProps {
sectionHeight: number;
dropBoxWidth: number;
isRootFolder: boolean;
onBackToParentFolder: TOnBackToParenFolder;
canCreate: boolean;
navigationItems: TNavigationItem[];
getContextOptionsFolder: TGetContextMenuModel;
getContextOptionsPlus: TGetContextMenuModel;
toggleInfoPanel: () => void;
toggleDropBox: () => void;
onClickAvailable: TOnNavigationItemClick;
isInfoPanelVisible: boolean;
isDesktop: boolean;
isDesktopClient: boolean;
withLogo: boolean;
burgerLogo: string;
currentDeviceType: DeviceType;
navigationTitleContainerNode: React.ReactNode;
}
export interface INavigationProps {
tReady: boolean;
showText: boolean;
isRootFolder: boolean;
title: string;
canCreate: boolean;
isTabletView: boolean;
onClickFolder: TOnNavigationItemClick;
navigationItems: TNavigationItem[];
onBackToParentFolder: TOnBackToParenFolder;
getContextOptionsFolder: TGetContextMenuModel;
getContextOptionsPlus: TGetContextMenuModel;
isTrashFolder: boolean;
isEmptyFilesList: boolean;
clearTrash: () => void;
showFolderInfo: () => void;
isCurrentFolderInfo: boolean;
toggleInfoPanel: () => void;
isInfoPanelVisible: boolean;
titles: TTitles;
withMenu: boolean;
onPlusClick: () => void;
isEmptyPage: boolean;
isDesktop: boolean;
isRoom: boolean;
isFrame: boolean;
hideInfoPanel: () => void;
withLogo: boolean;
burgerLogo: string;
showRootFolderTitle: boolean;
isPublicRoom: boolean;
titleIcon: string;
currentDeviceType: DeviceType;
rootRoomTitle: string;
}

View File

@ -0,0 +1,7 @@
import Navigation from "./Navigation";
import { TTitles, TNavigationItem } from "./Navigation.types";
export type { TTitles, TNavigationItem };
export default Navigation;

View File

@ -0,0 +1,27 @@
import React from "react";
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
import { IconButton } from "../../icon-button";
import { IArrowButtonProps } from "../Navigation.types";
const ArrowButton = ({
isRootFolder,
onBackToParentFolder,
}: IArrowButtonProps) => {
return !isRootFolder ? (
<div className="navigation-arrow-container">
<IconButton
iconName={ArrowPathReactSvgUrl}
size={17}
isFill
onClick={onBackToParentFolder}
className="arrow-button"
/>
<div className="navigation-header-separator" />
</div>
) : null;
};
export default React.memo(ArrowButton);

View File

@ -1,23 +1,36 @@
import React, { useState, useRef } from "react";
import PropTypes from "prop-types";
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/icons/17/vertical-dots.react.svg?url";
import { IconButton } from "@docspace/shared/components/icon-button";
import { ContextMenu } from "@docspace/shared/components/context-menu";
const ContextButton = (props) => {
import { IconButton } from "../../icon-button";
import { ContextMenu, TContextMenuRef } from "../../context-menu";
import { IContextButtonProps } from "../Navigation.types";
const ContextButton = ({
className,
getData,
withMenu = true,
isTrashFolder,
isMobile,
id,
...rest
}: IContextButtonProps) => {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
const menuRef = useRef(null);
const ref = useRef<HTMLDivElement | null>(null);
const menuRef = useRef<TContextMenuRef | null>(null);
const { className, getData, withMenu, isTrashFolder, isMobile, ...rest } =
props;
const toggle = (e: React.MouseEvent<HTMLDivElement>, open: boolean) => {
if (open) {
menuRef.current?.show(e);
} else {
menuRef.current?.hide(e);
}
const toggle = (e, isOpen) => {
isOpen ? menuRef.current.show(e) : menuRef.current.hide(e);
setIsOpen(isOpen);
setIsOpen(open);
};
const onClick = (e) => {
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (withMenu) toggle(e, !isOpen);
};
@ -32,7 +45,7 @@ const ContextButton = (props) => {
<IconButton
onClick={onClick}
iconName={VerticalDotsReactSvgUrl}
id={props.id}
id={id}
size={17}
isFill
/>
@ -48,14 +61,4 @@ const ContextButton = (props) => {
);
};
ContextButton.propTypes = {
className: PropTypes.string,
getData: PropTypes.func.isRequired,
id: PropTypes.string,
};
ContextButton.defaultProps = {
withMenu: true,
};
export default ContextButton;

View File

@ -0,0 +1,131 @@
import React from "react";
import { StyledControlButtonContainer } from "../Navigation.styled";
import { IControlButtonProps } from "../Navigation.types";
import ToggleInfoPanelButton from "./ToggleInfoPanelBtn";
import PlusButton from "./PlusBtn";
import ContextButton from "./ContextBtn";
const ControlButtons = ({
isRootFolder,
canCreate,
getContextOptionsFolder,
getContextOptionsPlus,
isEmptyFilesList,
isInfoPanelVisible,
toggleInfoPanel,
toggleDropBox,
isDesktop,
titles,
withMenu,
onPlusClick,
isFrame,
isPublicRoom,
isTrashFolder,
isMobile,
}: IControlButtonProps) => {
const toggleInfoPanelAction = () => {
toggleInfoPanel?.();
toggleDropBox?.();
};
return (
<StyledControlButtonContainer isFrame={isFrame}>
{!isRootFolder || (isTrashFolder && !isEmptyFilesList) ? (
<>
{!isMobile && canCreate && (
<PlusButton
className="add-button"
getData={getContextOptionsPlus}
withMenu={withMenu}
onPlusClick={onPlusClick}
isFrame={isFrame}
title={titles?.actions}
/>
)}
{/* <ContextMenuButton
id="header_optional-button"
zIndex={402}
className="option-button"
directionX="right"
iconName={VerticalDotsReactSvgUrl}
size={15}
isFill
getData={getContextOptionsFolder}
isDisabled={false}
title={titles?.contextMenu}
/> */}
<ContextButton
id="header_optional-button"
className="option-button"
getData={getContextOptionsFolder}
withMenu={withMenu}
// onPlusClick={onPlusClick}
title={titles?.actions}
isTrashFolder={isTrashFolder}
isMobile={isMobile || false}
/>
{!isDesktop && (
<ToggleInfoPanelButton
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
toggleInfoPanel={toggleInfoPanelAction}
titles={titles}
/>
)}
</>
) : canCreate ? (
<>
{!isMobile && (
<PlusButton
id="header_add-button"
className="add-button"
getData={getContextOptionsPlus}
withMenu={withMenu}
onPlusClick={onPlusClick}
isFrame={isFrame}
title={titles?.actions}
/>
)}
{!isDesktop && (
<ToggleInfoPanelButton
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
toggleInfoPanel={toggleInfoPanelAction}
titles={titles}
/>
)}
</>
) : (
<>
{!isDesktop && (
<ToggleInfoPanelButton
isRootFolder={isRootFolder}
isInfoPanelVisible={isInfoPanelVisible}
toggleInfoPanel={toggleInfoPanelAction}
titles={titles}
/>
)}
{isPublicRoom && (
<ContextButton
id="header_optional-button"
className="option-button"
getData={getContextOptionsFolder}
withMenu={withMenu}
title={titles?.contextMenu}
isTrashFolder={isTrashFolder}
isMobile={isMobile || false}
/>
)}
</>
)}
</StyledControlButtonContainer>
);
};
export default React.memo(ControlButtons);

View File

@ -0,0 +1,146 @@
import React, { useCallback } from "react";
import { useTheme } from "styled-components";
import { Direction, VariableSizeList } from "react-window";
import { DeviceType } from "../../../enums";
import { CustomScrollbarsVirtualList } from "../../scrollbar";
import { StyledBox, StyledContainer } from "../Navigation.styled";
import { IDropBoxProps } from "../Navigation.types";
import NavigationLogo from "./LogoBlock";
import ArrowButton from "./ArrowBtn";
import ControlButtons from "./ControlBtn";
import Row from "./Row";
const DropBox = React.forwardRef<HTMLDivElement, IDropBoxProps>(
(
{
sectionHeight,
dropBoxWidth,
isRootFolder,
onBackToParentFolder,
canCreate,
navigationItems,
getContextOptionsFolder,
getContextOptionsPlus,
toggleDropBox,
toggleInfoPanel,
onClickAvailable,
isInfoPanelVisible,
isDesktop,
isDesktopClient,
withLogo,
burgerLogo,
currentDeviceType,
navigationTitleContainerNode,
},
ref,
) => {
const [dropBoxHeight, setDropBoxHeight] = React.useState(0);
const countItems = navigationItems.length;
const getItemSize = useCallback(
(index: number): number => {
if (index === countItems - 1) return 51;
return currentDeviceType !== DeviceType.desktop ? 36 : 30;
},
[countItems, currentDeviceType],
);
const { interfaceDirection } = useTheme();
React.useEffect(() => {
const itemsHeight = navigationItems.map((item, index) =>
getItemSize(index),
);
const currentHeight = itemsHeight.reduce((a, b) => a + b);
let navHeight = 41;
if (currentDeviceType === DeviceType.tablet) {
navHeight = 49;
}
if (currentDeviceType === DeviceType.mobile) {
navHeight = 45;
}
setDropBoxHeight(
currentHeight + navHeight > sectionHeight
? sectionHeight - navHeight - 20
: currentHeight,
);
}, [sectionHeight, currentDeviceType, navigationItems, getItemSize]);
const isTabletView = currentDeviceType === DeviceType.tablet;
return (
<StyledBox
ref={ref}
height={sectionHeight < dropBoxHeight ? sectionHeight : null}
dropBoxWidth={dropBoxWidth}
withLogo={withLogo}
>
<StyledContainer
isDropBoxComponent
isInfoPanelVisible={isInfoPanelVisible}
isDesktopClient={isDesktopClient}
withLogo={!!withLogo && isTabletView}
isDesktop={isDesktop}
>
{withLogo && (
<NavigationLogo
burgerLogo={burgerLogo}
className="navigation-logo drop-box-logo"
/>
)}
<ArrowButton
isRootFolder={isRootFolder}
onBackToParentFolder={onBackToParentFolder}
/>
{navigationTitleContainerNode}
<ControlButtons
isDesktop={isDesktop}
isMobile={currentDeviceType !== DeviceType.desktop}
isRootFolder={isRootFolder}
canCreate={canCreate}
getContextOptionsFolder={getContextOptionsFolder}
getContextOptionsPlus={getContextOptionsPlus}
toggleInfoPanel={toggleInfoPanel}
toggleDropBox={toggleDropBox}
isInfoPanelVisible={isInfoPanelVisible}
/>
</StyledContainer>
<VariableSizeList
direction={interfaceDirection as Direction}
height={dropBoxHeight}
width="auto"
itemCount={countItems}
itemSize={getItemSize}
itemData={[
navigationItems,
onClickAvailable,
{ withLogo: !!withLogo, currentDeviceType },
]}
outerElementType={CustomScrollbarsVirtualList}
>
{Row}
</VariableSizeList>
</StyledBox>
);
},
);
DropBox.displayName = "DropBox";
export default React.memo(DropBox);

View File

@ -0,0 +1,63 @@
import React from "react";
import DefaultIcon from "PUBLIC_DIR/images/default.react.svg";
import RootIcon from "PUBLIC_DIR/images/root.react.svg";
import DefaultTabletIcon from "PUBLIC_DIR/images/default.tablet.react.svg";
import RootTabletIcon from "PUBLIC_DIR/images/root.tablet.react.svg";
import { DeviceType } from "../../../enums";
import { ColorTheme, ThemeId } from "../../color-theme";
import { StyledItem, StyledText } from "../Navigation.styled";
import { INavigationItemProps } from "../Navigation.types";
const Item = ({
id,
title,
isRoot,
isRootRoom,
onClick,
withLogo,
currentDeviceType,
...rest
}: INavigationItemProps) => {
const onClickAvailable = () => {
onClick?.(id, isRootRoom);
};
return (
<StyledItem
id={`${id}`}
isRoot={isRoot}
onClick={onClickAvailable}
withLogo={withLogo}
{...rest}
>
<ColorTheme isRoot={isRoot} themeId={ThemeId.IconWrapper}>
{currentDeviceType !== DeviceType.desktop ? (
isRoot ? (
<RootTabletIcon />
) : (
<DefaultTabletIcon />
)
) : isRoot ? (
<RootIcon />
) : (
<DefaultIcon />
)}
</ColorTheme>
<StyledText
isRoot={isRoot}
fontWeight={isRoot ? "600" : "400"}
fontSize="15px"
truncate
title={title}
>
{title}
</StyledText>
</StyledItem>
);
};
export default React.memo(Item);

View File

@ -0,0 +1,20 @@
import React from "react";
import { INavigationLogoProps } from "../Navigation.types";
const NavigationLogo = ({
logo,
burgerLogo,
...rest
}: INavigationLogoProps) => {
return (
<div {...rest}>
<img className="logo-icon_svg" alt="logo" src={logo} />
<div className="header-burger">
<img src={burgerLogo} alt="burger logo" /* onClick={onLogoClick} */ />
</div>
<div className="header_separator" />
</div>
);
};
export default NavigationLogo;

View File

@ -0,0 +1,65 @@
import React, { useState, useRef } from "react";
import PlusReactSvgUrl from "PUBLIC_DIR/images/icons/17/plus.svg?url";
import { IconButton } from "../../icon-button";
import { ContextMenu, TContextMenuRef } from "../../context-menu";
import { IPlusButtonProps } from "../Navigation.types";
const PlusButton = ({
className,
getData,
withMenu = true,
onPlusClick,
isFrame,
id,
...rest
}: IPlusButtonProps) => {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef<HTMLDivElement | null>(null);
const menuRef = useRef<TContextMenuRef | null>(null);
const toggle = (e: React.MouseEvent<HTMLDivElement>, open: boolean) => {
if (open) {
menuRef.current?.show(e);
} else {
menuRef.current?.hide(e);
}
setIsOpen(open);
};
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (withMenu) toggle(e, !isOpen);
else onPlusClick?.();
};
const onHide = () => {
setIsOpen(false);
};
const model = getData();
return (
<div ref={ref} className={className} {...rest}>
<IconButton
onClick={onClick}
iconName={PlusReactSvgUrl}
id={id}
size={17}
isFill
/>
<ContextMenu
model={model}
containerRef={ref}
ref={menuRef}
onHide={onHide}
scaled={false}
// directionX="right"
leftOffset={isFrame ? 190 : 150}
/>
</div>
);
};
export default PlusButton;

View File

@ -0,0 +1,35 @@
import React from "react";
import Item from "./Item";
import { TRowData } from "../Navigation.types";
const Row = React.memo(
({
data,
index,
style,
}: {
data: TRowData;
index: number;
style: React.CSSProperties;
}) => {
const isRoot = index === data[0].length - 1;
return (
<Item
key={data[0][index].id}
id={data[0][index].id}
title={data[0][index].title}
isRootRoom={data[0][index].isRootRoom}
isRoot={isRoot}
onClick={data[1]}
withLogo={data[2].withLogo}
currentDeviceType={data[2].currentDeviceType}
style={{ ...style }}
/>
);
},
);
Row.displayName = "Row";
export default Row;

View File

@ -0,0 +1,49 @@
import React from "react";
import {
StyledArrowIcon,
StyledExpanderDownIcon,
StyledExpanderDownIconRotate,
StyledHeading,
StyledTextContainer,
} from "../Navigation.styled";
import { ITextProps } from "../Navigation.types";
const Text = ({
title,
isRootFolder,
isOpen,
isRootFolderTitle,
onClick,
...rest
}: ITextProps) => {
return (
<StyledTextContainer
isRootFolder={isRootFolder}
onClick={onClick}
isRootFolderTitle={isRootFolderTitle}
{...rest}
>
<StyledHeading
title={title}
truncate
isRootFolderTitle={isRootFolderTitle}
>
{title}
</StyledHeading>
{isRootFolderTitle && <StyledArrowIcon />}
{!isRootFolderTitle && !isRootFolder ? (
isOpen ? (
<StyledExpanderDownIconRotate />
) : (
<StyledExpanderDownIcon />
)
) : null}
</StyledTextContainer>
);
};
export default React.memo(Text);

View File

@ -0,0 +1,38 @@
import React from "react";
import PanelReactSvgUrl from "PUBLIC_DIR/images/panel.react.svg?url";
import { IconButton } from "../../icon-button";
import { ThemeId } from "../../color-theme";
import { IToggleInfoPanelButtonProps } from "../Navigation.types";
import { StyledInfoPanelToggleColorThemeWrapper } from "../Navigation.styled";
const ToggleInfoPanelButton = ({
isRootFolder,
isInfoPanelVisible,
toggleInfoPanel,
id,
titles,
}: IToggleInfoPanelButtonProps) => {
return (
<StyledInfoPanelToggleColorThemeWrapper
isRootFolder={isRootFolder}
themeId={ThemeId.InfoPanelToggle}
isInfoPanelVisible={isInfoPanelVisible}
>
<div className="info-panel-toggle-bg">
<IconButton
id={id}
className="info-panel-toggle"
iconName={PanelReactSvgUrl}
size={16}
isFill
title={titles?.infoPanel}
onClick={toggleInfoPanel}
/>
</div>
</StyledInfoPanelToggleColorThemeWrapper>
);
};
export default ToggleInfoPanelButton;

View File

@ -0,0 +1,10 @@
import React from "react";
import { StyledTrashWarning } from "../Navigation.styled";
const TrashWarning = ({ title }: { title?: string }) => {
return (
<StyledTrashWarning className="trash-warning">{title}</StyledTrashWarning>
);
};
export default TrashWarning;

View File

@ -1,6 +1,6 @@
import React from "react";
const defaultValue = {};
const defaultValue: { sectionWidth?: number; sectionHeight?: number } = {};
export const Context = React.createContext(defaultValue);