Merge pull request #422 from ONLYOFFICE/feature/new-context-menu
Feature/new context menu
This commit is contained in:
commit
f1a184f8c5
@ -1256,71 +1256,10 @@ class ContextOptionsStore {
|
||||
!contextOptions.includes("finalize-version") &&
|
||||
contextOptions.includes("show-version-history");
|
||||
|
||||
const versionActions = isDesktop()
|
||||
? onlyShowVersionHistory
|
||||
? [
|
||||
{
|
||||
id: "option_show-version-history",
|
||||
key: "show-version-history",
|
||||
label: t("ShowVersionHistory"),
|
||||
icon: HistoryReactSvgUrl,
|
||||
onClick: () =>
|
||||
this.showVersionHistory(
|
||||
item.id,
|
||||
item.security,
|
||||
item?.requestToken,
|
||||
),
|
||||
disabled: false,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: "option_version",
|
||||
key: "version",
|
||||
label: t("VersionHistory"),
|
||||
icon: HistoryFinalizedReactSvgUrl,
|
||||
items: [
|
||||
{
|
||||
id: "option_finalize-version",
|
||||
key: "finalize-version",
|
||||
label: t("FinalizeVersion"),
|
||||
icon: HistoryFinalizedReactSvgUrl,
|
||||
onClick: () =>
|
||||
isEditing
|
||||
? this.onShowEditingToast(t)
|
||||
: this.finalizeVersion(item.id, item.security),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: "option_version-history",
|
||||
key: "show-version-history",
|
||||
label: t("ShowVersionHistory"),
|
||||
icon: HistoryReactSvgUrl,
|
||||
onClick: () =>
|
||||
this.showVersionHistory(
|
||||
item.id,
|
||||
item.security,
|
||||
item?.requestToken,
|
||||
),
|
||||
disabled: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: [
|
||||
const versionActions = onlyShowVersionHistory
|
||||
? [
|
||||
{
|
||||
id: "option_finalize-version",
|
||||
key: "finalize-version",
|
||||
label: t("FinalizeVersion"),
|
||||
icon: HistoryFinalizedReactSvgUrl,
|
||||
onClick: () =>
|
||||
isEditing
|
||||
? this.onShowEditingToast(t)
|
||||
: this.finalizeVersion(item.id),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: "option_version-history",
|
||||
id: "option_show-version-history",
|
||||
key: "show-version-history",
|
||||
label: t("ShowVersionHistory"),
|
||||
icon: HistoryReactSvgUrl,
|
||||
@ -1332,8 +1271,43 @@ class ContextOptionsStore {
|
||||
),
|
||||
disabled: false,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: "option_version",
|
||||
key: "version",
|
||||
label: t("VersionHistory"),
|
||||
icon: HistoryFinalizedReactSvgUrl,
|
||||
items: [
|
||||
{
|
||||
id: "option_finalize-version",
|
||||
key: "finalize-version",
|
||||
label: t("FinalizeVersion"),
|
||||
icon: HistoryFinalizedReactSvgUrl,
|
||||
onClick: () =>
|
||||
isEditing
|
||||
? this.onShowEditingToast(t)
|
||||
: this.finalizeVersion(item.id, item.security),
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
id: "option_version-history",
|
||||
key: "show-version-history",
|
||||
label: t("ShowVersionHistory"),
|
||||
icon: HistoryReactSvgUrl,
|
||||
onClick: () =>
|
||||
this.showVersionHistory(
|
||||
item.id,
|
||||
item.security,
|
||||
item?.requestToken,
|
||||
),
|
||||
disabled: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const moveActions = isDesktop()
|
||||
|
||||
const moveActions = !isInfoPanel
|
||||
? [
|
||||
{
|
||||
id: "option_move-or-copy",
|
||||
@ -1815,7 +1789,7 @@ class ContextOptionsStore {
|
||||
const pluginItems = this.onLoadPlugins(item);
|
||||
|
||||
if (pluginItems.length > 0) {
|
||||
if (!isDesktop() || pluginItems.length === 1) {
|
||||
if (pluginItems.length === 1) {
|
||||
pluginItems.forEach((plugin) => {
|
||||
options.splice(1, 0, {
|
||||
id: `option_${plugin.key}`,
|
||||
|
@ -26,21 +26,7 @@
|
||||
|
||||
import styled, { css } from "styled-components";
|
||||
import { Base, TTheme, globalColors } from "../../themes";
|
||||
import { tablet, mobile, getCorrectFourValuesStyle } from "../../utils";
|
||||
|
||||
const styledTabletView = css<{ articleWidth: number }>`
|
||||
position: fixed;
|
||||
width: ${(props) => props.theme.newContextMenu.devices.tabletWidth};
|
||||
max-width: ${(props) => props.theme.newContextMenu.devices.tabletWidth};
|
||||
max-height: ${(props) => props.theme.newContextMenu.devices.maxHeight};
|
||||
inset-inline-start: ${(props) =>
|
||||
props.articleWidth
|
||||
? `${props.articleWidth}px`
|
||||
: props.theme.newContextMenu.devices.left};
|
||||
inset-inline-end: ${(props) => props.theme.newContextMenu.devices.right};
|
||||
bottom: ${(props) => props.theme.newContextMenu.devices.bottom};
|
||||
margin: ${(props) => props.theme.newContextMenu.devices.margin};
|
||||
`;
|
||||
import { mobile, getCorrectFourValuesStyle } from "../../utils";
|
||||
|
||||
const styledMobileView = css`
|
||||
position: fixed;
|
||||
@ -97,14 +83,13 @@ const StyledContextMenu = styled.div<{
|
||||
box-shadow: ${(props) => props.theme.newContextMenu.boxShadow};
|
||||
-moz-box-shadow: ${(props) => props.theme.newContextMenu.boxShadow};
|
||||
-webkit-box-shadow: ${(props) => props.theme.newContextMenu.boxShadow};
|
||||
padding: ${(props) => props.theme.newContextMenu.padding};
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) => props.changeView && styledTabletView}
|
||||
}
|
||||
.scroll-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
@media ${mobile} {
|
||||
${(props) => props.changeView && styledMobileView}
|
||||
padding-inline-end: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +190,6 @@ const StyledContextMenu = styled.div<{
|
||||
box-shadow: ${(props) => props.theme.dropDown.boxShadow};
|
||||
-moz-box-shadow: ${(props) => props.theme.dropDown.boxShadow};
|
||||
-webkit-box-shadow: ${(props) => props.theme.dropDown.boxShadow};
|
||||
padding: 4px 0px;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@ -226,7 +210,7 @@ const StyledContextMenu = styled.div<{
|
||||
position: relative;
|
||||
border: ${(props) => props.theme.dropDownItem.border};
|
||||
margin: ${(props) => props.theme.dropDownItem.margin};
|
||||
padding: ${(props) => props.theme.dropDownItem.padding};
|
||||
padding: 0 16px;
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
font-style: normal;
|
||||
background: none;
|
||||
@ -241,10 +225,6 @@ const StyledContextMenu = styled.div<{
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
|
||||
@media ${tablet} {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
props.noHover
|
||||
@ -275,7 +255,7 @@ const StyledContextMenu = styled.div<{
|
||||
cursor: default !important;
|
||||
margin: ${(props) => props.theme.menuItem.separator.margin};
|
||||
height: ${(props) => props.theme.menuItem.separator.height};
|
||||
width: ${(props) => props.theme.menuItem.separator.width};
|
||||
|
||||
&:hover {
|
||||
cursor: default !important;
|
||||
}
|
||||
@ -284,6 +264,14 @@ const StyledContextMenu = styled.div<{
|
||||
.p-contextmenu .p-menuitem {
|
||||
position: relative;
|
||||
margin: ${(props) => props.theme.dropDownItem.margin};
|
||||
|
||||
max-width: calc(-32px + 100vw);
|
||||
width: fit-content;
|
||||
min-width: inherit;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.p-contextmenu .scroll-body .p-menuitem {
|
||||
@ -361,10 +349,47 @@ const StyledContextMenu = styled.div<{
|
||||
opacity: 1;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
.p-contextmenu {
|
||||
@media ${mobile} {
|
||||
${(props) => props.changeView && styledMobileView}
|
||||
}
|
||||
|
||||
@media not ${mobile} {
|
||||
max-width: calc(100vw - 32px);
|
||||
}
|
||||
}
|
||||
|
||||
.p-contextmenu ul {
|
||||
@media not ${mobile} {
|
||||
max-width: calc(100vw - 32px);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledContextMenu.defaultProps = {
|
||||
theme: Base,
|
||||
};
|
||||
|
||||
export const StyledList = styled.ul<{
|
||||
listHeight: number;
|
||||
widthSubMenu: null | number;
|
||||
}>`
|
||||
& > :first-child {
|
||||
.scroll-body {
|
||||
height: ${(props) => `${props.listHeight}px`};
|
||||
}
|
||||
}
|
||||
|
||||
& > :nth-child(1) {
|
||||
${(props) =>
|
||||
props.widthSubMenu &&
|
||||
css`
|
||||
.p-menuitem {
|
||||
max-width: ${`${props.widthSubMenu}px`};
|
||||
}
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledContextMenu };
|
||||
|
@ -57,6 +57,8 @@ import {
|
||||
ContextMenuRefType,
|
||||
} from "./ContextMenu.types";
|
||||
|
||||
const marginBorder = 16; // Indentation from the border of the screen
|
||||
|
||||
const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
(props, ref) => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
@ -65,9 +67,11 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
const [model, setModel] = React.useState<ContextMenuModel[] | null>(null);
|
||||
const [changeView, setChangeView] = React.useState(false);
|
||||
const [showMobileMenu, setShowMobileMenu] = React.useState(false);
|
||||
const [onLoad, setOnLoad] = React.useState<
|
||||
undefined | (() => Promise<ContextMenuModel[]>)
|
||||
>(undefined);
|
||||
const [mobileSubMenuItems, setMobileSubMenuItems] = React.useState<
|
||||
ContextMenuModel[] | undefined
|
||||
>([]);
|
||||
const [mobileHeader, setMobileHeader] = React.useState<string>("");
|
||||
|
||||
const [articleWidth, setArticleWidth] = React.useState(0);
|
||||
|
||||
const prevReshow = React.useRef(false);
|
||||
@ -211,7 +215,7 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
? rects.left - currentLeftOffset - currentRightOffset
|
||||
: event.pageX + 1;
|
||||
let top = rects ? rects.top : event.pageY + 1;
|
||||
const width =
|
||||
let width =
|
||||
menuRef.current && menuRef.current.offsetParent
|
||||
? menuRef.current.offsetWidth
|
||||
: DomHelpers.getHiddenElementOuterWidth(menuRef.current);
|
||||
@ -221,29 +225,30 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
: DomHelpers.getHiddenElementOuterHeight(menuRef.current);
|
||||
const viewport = DomHelpers.getViewport();
|
||||
|
||||
const mobileView =
|
||||
isMobileUtils() && (height > 210 || ignoreChangeView);
|
||||
|
||||
if (!mobileView) {
|
||||
const options =
|
||||
menuRef?.current?.getElementsByClassName("p-menuitem");
|
||||
const optionsWidth: number[] = [];
|
||||
|
||||
if (options) {
|
||||
Array.from(options).forEach((option) =>
|
||||
optionsWidth.push(option.clientWidth),
|
||||
);
|
||||
|
||||
const widthMaxContent = Math.max(...optionsWidth);
|
||||
|
||||
width = widthMaxContent;
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.interfaceDirection === "rtl" && !rects && left > width) {
|
||||
left = event.pageX - width + 1;
|
||||
}
|
||||
|
||||
if (
|
||||
isTabletUtils() &&
|
||||
(height > 483 ||
|
||||
(isMobileOnly && window.innerHeight < window.innerWidth))
|
||||
) {
|
||||
const article = document.getElementById("article-container");
|
||||
|
||||
let currentArticleWidth = 0;
|
||||
if (article) {
|
||||
currentArticleWidth = article.offsetWidth;
|
||||
}
|
||||
|
||||
setChangeView(true);
|
||||
setArticleWidth(currentArticleWidth);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMobileUtils() && (height > 210 || ignoreChangeView)) {
|
||||
if (mobileView) {
|
||||
setChangeView(true);
|
||||
setArticleWidth(0);
|
||||
|
||||
@ -267,7 +272,8 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
|
||||
// fit
|
||||
if (top < document.body.scrollTop) {
|
||||
top = document.body.scrollTop;
|
||||
if (document.body.scrollTop === 0) top = marginBorder;
|
||||
else top = document.body.scrollTop;
|
||||
}
|
||||
|
||||
if (containerRef) {
|
||||
@ -281,8 +287,10 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
}
|
||||
}
|
||||
if (menuRef.current) {
|
||||
menuRef.current.style.left = `${left}px`;
|
||||
menuRef.current.style.left = `${left || marginBorder}px`;
|
||||
menuRef.current.style.top = `${top}px`;
|
||||
|
||||
if (!mobileView) menuRef.current.style.width = `${width}px`;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -410,14 +418,20 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
};
|
||||
}, [documentResizeListener, onHide, visible]);
|
||||
|
||||
const onMobileItemClick = (
|
||||
const onMobileItemClick = async (
|
||||
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
|
||||
label: string,
|
||||
items?: ContextMenuModel[],
|
||||
loadFunc?: () => Promise<ContextMenuModel[]>,
|
||||
) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setShowMobileMenu(true);
|
||||
if (loadFunc) setOnLoad(loadFunc);
|
||||
|
||||
const res = loadFunc ? await loadFunc() : items;
|
||||
setMobileSubMenuItems(res);
|
||||
|
||||
setMobileHeader(label);
|
||||
};
|
||||
|
||||
const onBackClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
@ -517,7 +531,7 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
)}
|
||||
|
||||
<Text className="text" truncate dir="auto">
|
||||
{header.title}
|
||||
{showMobileMenu ? mobileHeader : header.title}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
@ -527,7 +541,7 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
root
|
||||
resetMenu={resetMenu}
|
||||
onLeafClick={onLeafClick}
|
||||
onLoad={onLoad}
|
||||
mobileSubMenuItems={mobileSubMenuItems}
|
||||
/>
|
||||
) : (
|
||||
<SubMenu
|
||||
@ -535,8 +549,9 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
|
||||
root
|
||||
resetMenu={resetMenu}
|
||||
onLeafClick={onLeafClick}
|
||||
changeView={changeView}
|
||||
onMobileItemClick={onMobileItemClick}
|
||||
changeView={changeView}
|
||||
withHeader={withHeader}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -44,9 +44,9 @@ const MobileSubMenu = (props: {
|
||||
onLeafClick: (e: React.MouseEvent) => void;
|
||||
root?: boolean;
|
||||
resetMenu: boolean;
|
||||
onLoad?: () => Promise<ContextMenuModel[]>;
|
||||
mobileSubMenuItems?: ContextMenuModel[];
|
||||
}) => {
|
||||
const { onLeafClick, root, resetMenu, onLoad } = props;
|
||||
const { onLeafClick, root, resetMenu, mobileSubMenuItems } = props;
|
||||
|
||||
const [submenu, setSubmenu] = useState<null | ContextMenuModel[]>(null);
|
||||
|
||||
@ -91,16 +91,12 @@ const MobileSubMenu = (props: {
|
||||
}
|
||||
});
|
||||
|
||||
const fetchSubMenu = React.useCallback(async () => {
|
||||
const res = await onLoad?.();
|
||||
if (res) setSubmenu(res);
|
||||
|
||||
position();
|
||||
}, [position, setSubmenu, onLoad]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onLoad) fetchSubMenu();
|
||||
}, [onLoad, fetchSubMenu]);
|
||||
if (!mobileSubMenuItems?.length) return;
|
||||
|
||||
setSubmenu(mobileSubMenuItems);
|
||||
position();
|
||||
}, [mobileSubMenuItems, mobileSubMenuItems?.length, position]);
|
||||
|
||||
const onItemClick = (e: React.MouseEvent, item: ContextMenuType) => {
|
||||
const { disabled, url, onClick, items, action } = item;
|
||||
|
@ -33,20 +33,22 @@ import { useTheme } from "styled-components";
|
||||
import ArrowIcon from "PUBLIC_DIR/images/arrow.right.react.svg";
|
||||
import OutsdideIcon from "PUBLIC_DIR/images/arrow.outside.react.svg";
|
||||
import { isMobile as isMobileDevice } from "react-device-detect";
|
||||
|
||||
import { classNames, ObjectUtils, DomHelpers } from "../../../utils";
|
||||
import { classNames, ObjectUtils, DomHelpers, isMobile } from "../../../utils";
|
||||
import { ContextMenuSkeleton } from "../../../skeletons/context-menu";
|
||||
|
||||
import { Scrollbar } from "../../scrollbar";
|
||||
import { ToggleButton } from "../../toggle-button";
|
||||
import { Scrollbar } from "../../scrollbar";
|
||||
|
||||
import { SubMenuItem } from "../ContextMenu.styled";
|
||||
import { SubMenuItem, StyledList } from "../ContextMenu.styled";
|
||||
import {
|
||||
ContextMenuModel,
|
||||
ContextMenuType,
|
||||
SeparatorType,
|
||||
} from "../ContextMenu.types";
|
||||
|
||||
const submenuListMargin = 4; // Indentation of the second level menu from the first level
|
||||
const sectionPadding = 16; // Screen margin
|
||||
|
||||
const SubMenu = (props: {
|
||||
model: ContextMenuModel[];
|
||||
root?: boolean;
|
||||
@ -57,23 +59,28 @@ const SubMenu = (props: {
|
||||
) => void;
|
||||
onMobileItemClick?: (
|
||||
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
|
||||
loadFunc: () => Promise<ContextMenuModel[]>,
|
||||
label: string,
|
||||
items?: ContextMenuModel[],
|
||||
loadFunc?: () => Promise<ContextMenuModel[]>,
|
||||
) => void;
|
||||
changeView?: boolean;
|
||||
onLoad?: () => Promise<ContextMenuModel[]>;
|
||||
changeView?: boolean;
|
||||
withHeader?: boolean;
|
||||
}) => {
|
||||
const {
|
||||
onLeafClick,
|
||||
root,
|
||||
resetMenu,
|
||||
changeView,
|
||||
onMobileItemClick,
|
||||
onLoad,
|
||||
changeView,
|
||||
withHeader,
|
||||
} = props;
|
||||
|
||||
const [model, setModel] = useState(props?.model);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeItem, setActiveItem] = useState<ContextMenuType | null>(null);
|
||||
const [widthSubMenu, setWidthSubMenu] = useState<null | number>(null);
|
||||
|
||||
const subMenuRef = useRef<HTMLUListElement>(null);
|
||||
|
||||
@ -92,15 +99,19 @@ const SubMenu = (props: {
|
||||
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
|
||||
item: ContextMenuType,
|
||||
) => {
|
||||
const { url, onClick, items, action } = item;
|
||||
const { disabled, url, onClick, items, action, label } = item;
|
||||
|
||||
if (item.onLoad) {
|
||||
if (isMobile() && label && (items || item.onLoad)) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!isMobileDevice) return;
|
||||
|
||||
onMobileItemClick?.(e, item.onLoad);
|
||||
if (items) onMobileItemClick?.(e, label as string, items, undefined);
|
||||
else if (item.onLoad)
|
||||
onMobileItemClick?.(e, label as string, undefined, item.onLoad);
|
||||
return;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -126,7 +137,9 @@ const SubMenu = (props: {
|
||||
const containerOffset = DomHelpers.getOffset(parentItem);
|
||||
const viewport = DomHelpers.getViewport();
|
||||
|
||||
const subListWidth = subMenuRef.current?.offsetParent
|
||||
const options = subMenuRef.current?.getElementsByClassName("p-menuitem");
|
||||
|
||||
let subListWidth = subMenuRef.current?.offsetParent
|
||||
? subMenuRef.current.offsetWidth
|
||||
: DomHelpers.getHiddenElementOuterWidth(subMenuRef.current);
|
||||
|
||||
@ -136,42 +149,90 @@ const SubMenu = (props: {
|
||||
|
||||
const isRtl = theme.interfaceDirection === "rtl";
|
||||
|
||||
if (!isMobile() && options) {
|
||||
const optionsWidth: number[] = [];
|
||||
Array.from(options).forEach((option) =>
|
||||
optionsWidth.push(Math.ceil(option.getBoundingClientRect().width)),
|
||||
);
|
||||
|
||||
const widthMaxContent = Math.max(...optionsWidth);
|
||||
|
||||
if (root) subListWidth = subListWidth || widthMaxContent;
|
||||
else if (!subMenuRef?.current?.style.width)
|
||||
subListWidth = Math.max(subListWidth, widthMaxContent);
|
||||
}
|
||||
|
||||
if (subMenuRef.current) {
|
||||
subMenuRef.current.style.top = "0px";
|
||||
let subMenuRefTop = null;
|
||||
|
||||
if (!isMobile()) {
|
||||
if (root) subMenuRef.current.style.width = `${subListWidth}px`;
|
||||
else if (!subMenuRef?.current?.style.width) {
|
||||
subMenuRef.current.style.width = `${subListWidth}px`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMobile() && !root) {
|
||||
const firstList = parentItem?.firstChild as HTMLElement;
|
||||
|
||||
const menuItemActive = firstList.querySelector(
|
||||
".p-menuitem-active",
|
||||
) as HTMLElement;
|
||||
|
||||
const top = menuItemActive.offsetTop;
|
||||
const scroller = firstList.querySelector(".scroller") as HTMLElement;
|
||||
const scrollTop = scroller.scrollTop;
|
||||
const positionActiveItem = top - scrollTop;
|
||||
|
||||
subMenuRefTop = positionActiveItem - 2;
|
||||
subMenuRef.current.style.top = `${subMenuRefTop}px`;
|
||||
}
|
||||
|
||||
const submenuRects = subMenuRef.current.getBoundingClientRect();
|
||||
|
||||
if (submenuRects.bottom > viewport.height) {
|
||||
if (submenuRects.bottom > viewport.height && subMenuRefTop) {
|
||||
const submenuMargin = 16;
|
||||
const topOffset = submenuRects.bottom - viewport.height + submenuMargin;
|
||||
|
||||
subMenuRef.current.style.top = `${-1 * topOffset}px`;
|
||||
const topOffset =
|
||||
subMenuRefTop -
|
||||
(submenuRects.bottom - viewport.height) -
|
||||
submenuMargin;
|
||||
|
||||
subMenuRef.current.style.top = `${topOffset}px`;
|
||||
}
|
||||
|
||||
const containerOffsetLeft = parseInt(`${containerOffset.left}`, 10);
|
||||
const freeSpaceRight =
|
||||
viewport.width - containerOffsetLeft - itemOuterWidth;
|
||||
const freeSpaceLeft = containerOffsetLeft;
|
||||
const submenuListMargin = 4;
|
||||
const sectionPadding = 17;
|
||||
|
||||
if (isRtl) {
|
||||
if (
|
||||
!root &&
|
||||
freeSpaceLeft > freeSpaceRight &&
|
||||
subListWidth > containerOffsetLeft
|
||||
) {
|
||||
// If the menu extends beyond the screen
|
||||
subMenuRef.current.style.width = `${containerOffsetLeft - submenuListMargin - sectionPadding}px`;
|
||||
}
|
||||
|
||||
if (
|
||||
subListWidth < containerOffsetLeft ||
|
||||
(!root && freeSpaceLeft > freeSpaceRight)
|
||||
) {
|
||||
subMenuRef.current.style.left = `${-1 * subListWidth}px`;
|
||||
|
||||
if (!root && subListWidth > containerOffsetLeft) {
|
||||
// If the menu extends beyond the screen
|
||||
const newWidth =
|
||||
containerOffsetLeft - submenuListMargin - sectionPadding;
|
||||
|
||||
subMenuRef.current.style.width = `${newWidth}px`;
|
||||
setWidthSubMenu(newWidth);
|
||||
}
|
||||
} else {
|
||||
subMenuRef.current.style.left = `${itemOuterWidth}px`;
|
||||
|
||||
if (!root) subMenuRef.current.style.marginLeft = `4px`;
|
||||
|
||||
if (!root && subListWidth > freeSpaceRight) {
|
||||
// If the menu extends beyond the screen
|
||||
const newWidth = freeSpaceRight - 3 * submenuListMargin;
|
||||
|
||||
subMenuRef.current.style.width = `${newWidth}px`;
|
||||
setWidthSubMenu(newWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,8 +241,22 @@ const SubMenu = (props: {
|
||||
viewport.width - DomHelpers.calculateScrollbarWidth();
|
||||
|
||||
if (!isRtl) {
|
||||
if (notEnoughWidthRight && containerOffsetLeft > subListWidth) {
|
||||
if (notEnoughWidthRight && freeSpaceLeft > freeSpaceRight) {
|
||||
subMenuRef.current.style.left = `${-1 * subListWidth}px`;
|
||||
|
||||
if (!root) subMenuRef.current.style.marginLeft = `-4px`;
|
||||
|
||||
if (
|
||||
notEnoughWidthRight &&
|
||||
!root &&
|
||||
subListWidth > containerOffsetLeft
|
||||
) {
|
||||
// If the menu extends beyond the screen
|
||||
const newWidth = containerOffsetLeft - 12;
|
||||
|
||||
subMenuRef.current.style.width = `${newWidth}px`;
|
||||
setWidthSubMenu(newWidth);
|
||||
}
|
||||
} else {
|
||||
subMenuRef.current.style.left = `${itemOuterWidth}px`;
|
||||
|
||||
@ -195,6 +270,7 @@ const SubMenu = (props: {
|
||||
sectionPadding;
|
||||
|
||||
subMenuRef.current.style.width = `${newWidth}px`;
|
||||
setWidthSubMenu(newWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -231,29 +307,6 @@ const SubMenu = (props: {
|
||||
/>
|
||||
);
|
||||
|
||||
const renderSubMenu = (item: ContextMenuType) => {
|
||||
const loaderItem = {
|
||||
id: "link-loader-option",
|
||||
key: "link-loader",
|
||||
isLoader: true,
|
||||
label: <ContextMenuSkeleton />,
|
||||
};
|
||||
|
||||
if (item.items || item.onLoad) {
|
||||
return (
|
||||
<SubMenu
|
||||
model={item.onLoad ? [loaderItem] : item.items || []}
|
||||
resetMenu={item !== activeItem}
|
||||
onLeafClick={onLeafClick}
|
||||
// onEnter={onEnter}
|
||||
onLoad={item.onLoad}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderMenuitem = (
|
||||
item: ContextMenuType,
|
||||
index: number,
|
||||
@ -301,7 +354,7 @@ const SubMenu = (props: {
|
||||
const subMenuIcon = (item.items || item.onLoad) && (
|
||||
<ArrowIcon className={subMenuIconClassName} />
|
||||
);
|
||||
const subMenu = renderSubMenu(item);
|
||||
|
||||
const dataKeys = Object.fromEntries(
|
||||
Object.entries(item).filter((el) => el[0].indexOf("data-") === 0),
|
||||
);
|
||||
@ -367,7 +420,6 @@ const SubMenu = (props: {
|
||||
onMouseEnter={(e) => onItemMouseEnter(e, item)}
|
||||
>
|
||||
{content}
|
||||
{subMenu}
|
||||
<ToggleButton
|
||||
isChecked={item.checked || false}
|
||||
onChange={onClick}
|
||||
@ -389,7 +441,6 @@ const SubMenu = (props: {
|
||||
onMouseEnter={(e) => onItemMouseEnter(e, item)}
|
||||
>
|
||||
{content}
|
||||
{subMenu}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@ -428,78 +479,100 @@ const SubMenu = (props: {
|
||||
};
|
||||
|
||||
const renderMenu = () => {
|
||||
if (model) {
|
||||
if (changeView) {
|
||||
const newModel = model.filter(
|
||||
(item: ContextMenuModel) => item && !item.disabled,
|
||||
if (!model) return null;
|
||||
|
||||
return model.map((item: ContextMenuModel, index: number) => {
|
||||
if (item?.disabled) return null;
|
||||
return renderItem(item, index);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSubMenuLower = () => {
|
||||
if (!model) return null;
|
||||
const submenu: JSX.Element[] = [];
|
||||
|
||||
const loaderItem = {
|
||||
id: "link-loader-option",
|
||||
key: "link-loader",
|
||||
isLoader: true,
|
||||
label: <ContextMenuSkeleton />,
|
||||
};
|
||||
|
||||
model.forEach((item) => {
|
||||
const contextMenuTypeItem = item as ContextMenuType;
|
||||
|
||||
if (contextMenuTypeItem?.items || contextMenuTypeItem?.onLoad) {
|
||||
submenu.push(
|
||||
<SubMenu
|
||||
key={`sub-menu_${item.id}`}
|
||||
model={
|
||||
contextMenuTypeItem?.onLoad
|
||||
? [loaderItem]
|
||||
: contextMenuTypeItem?.items || []
|
||||
}
|
||||
resetMenu={item !== activeItem}
|
||||
onLeafClick={onLeafClick}
|
||||
onLoad={contextMenuTypeItem?.onLoad}
|
||||
/>,
|
||||
);
|
||||
const rowHeights: number[] = newModel.map((item: ContextMenuModel) => {
|
||||
if (!item) return 0;
|
||||
if (item.isSeparator) return 13;
|
||||
return 36;
|
||||
});
|
||||
|
||||
// const getItemSize = (index) => rowHeights[index];
|
||||
|
||||
const height = rowHeights.reduce((a, b) => a + b);
|
||||
const viewport = DomHelpers.getViewport();
|
||||
|
||||
const listHeight =
|
||||
height + 61 > viewport.height - 64
|
||||
? viewport.height - 125
|
||||
: height + 5;
|
||||
|
||||
return (
|
||||
<Scrollbar style={{ height: listHeight }}>
|
||||
{model.map((item: ContextMenuModel, index: number) => {
|
||||
if (!item || item?.disabled) return null;
|
||||
|
||||
return renderItem(item, index);
|
||||
})}
|
||||
</Scrollbar>
|
||||
);
|
||||
|
||||
// return (
|
||||
// <VariableSizeList
|
||||
// height={listHeight}
|
||||
// width={"auto"}
|
||||
// itemCount={newModel.length}
|
||||
// itemSize={getItemSize}
|
||||
// itemData={newModel}
|
||||
// outerElementType={CustomScrollbarsVirtualList}
|
||||
// >
|
||||
// {renderItem}
|
||||
// </VariableSizeList>
|
||||
// );
|
||||
}
|
||||
});
|
||||
|
||||
return model.map((item: ContextMenuModel, index: number) => {
|
||||
if (item?.disabled) return null;
|
||||
return renderItem(item, index);
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
return submenu;
|
||||
};
|
||||
|
||||
const className = classNames({ "p-submenu-list": !root });
|
||||
const submenu = renderMenu();
|
||||
const active = isActive();
|
||||
const submenuLower = renderSubMenuLower();
|
||||
|
||||
return (
|
||||
<CSSTransition
|
||||
nodeRef={subMenuRef}
|
||||
classNames="p-contextmenusub"
|
||||
in={active}
|
||||
timeout={{ enter: 0, exit: 0 }}
|
||||
unmountOnExit
|
||||
onEnter={onEnter}
|
||||
>
|
||||
<ul ref={subMenuRef} className={`${className} not-selectable`}>
|
||||
{submenu}
|
||||
</ul>
|
||||
</CSSTransition>
|
||||
);
|
||||
if (model.length) {
|
||||
const newModel = model.filter(
|
||||
(item: ContextMenuModel) => item && !item.disabled,
|
||||
);
|
||||
const rowHeights: number[] = newModel.map((item: ContextMenuModel) => {
|
||||
if (!item) return 0;
|
||||
if (item.isSeparator) return 13;
|
||||
return 36;
|
||||
});
|
||||
|
||||
const height = rowHeights.reduce((a, b) => a + b);
|
||||
const viewport = DomHelpers.getViewport();
|
||||
const paddingList = 12;
|
||||
const marginsList = 32;
|
||||
const backdrop = 64;
|
||||
const header = 55;
|
||||
|
||||
const listHeight =
|
||||
changeView && withHeader
|
||||
? height + paddingList + header > viewport.height
|
||||
? viewport.height - backdrop - header - paddingList
|
||||
: height + paddingList
|
||||
: height + paddingList + marginsList > viewport.height
|
||||
? viewport.height - marginsList
|
||||
: height + paddingList;
|
||||
|
||||
return (
|
||||
<CSSTransition
|
||||
nodeRef={subMenuRef}
|
||||
classNames="p-contextmenusub"
|
||||
in={active}
|
||||
timeout={{ enter: 0, exit: 0 }}
|
||||
unmountOnExit
|
||||
onEnter={onEnter}
|
||||
>
|
||||
<StyledList
|
||||
ref={subMenuRef}
|
||||
className={`${className} not-selectable`}
|
||||
listHeight={height + paddingList}
|
||||
widthSubMenu={widthSubMenu}
|
||||
>
|
||||
<Scrollbar style={{ height: listHeight }}>{submenu}</Scrollbar>
|
||||
{submenuLower}
|
||||
</StyledList>
|
||||
</CSSTransition>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export { SubMenu };
|
||||
|
@ -2511,9 +2511,8 @@ export const getBaseTheme = () => {
|
||||
},
|
||||
separator: {
|
||||
borderBottom: `1px solid ${grayLightMid} !important`,
|
||||
margin: "6px 16px 6px 16px !important",
|
||||
margin: "6px 16px !important",
|
||||
height: "1px !important",
|
||||
width: "calc(100% - 32px) !important",
|
||||
},
|
||||
text: {
|
||||
header: {
|
||||
@ -2534,7 +2533,7 @@ export const getBaseTheme = () => {
|
||||
background: "none",
|
||||
svgFill: black,
|
||||
header: {
|
||||
height: "49px",
|
||||
height: "55px",
|
||||
borderBottom: `1px solid ${grayLightMid}`,
|
||||
marginBottom: "6px",
|
||||
},
|
||||
@ -2544,7 +2543,7 @@ export const getBaseTheme = () => {
|
||||
padding: "0 12px",
|
||||
mobile: {
|
||||
height: "36px",
|
||||
padding: "0 16px 6px",
|
||||
padding: "6px 16px",
|
||||
},
|
||||
},
|
||||
newContextMenu: {
|
||||
@ -2552,7 +2551,6 @@ export const getBaseTheme = () => {
|
||||
borderRadius: "6px",
|
||||
mobileBorderRadius: "6px 6px 0 0",
|
||||
boxShadow: `0px 8px 16px 0px ${boxShadowColor}`,
|
||||
padding: "6px 0px",
|
||||
border: "none",
|
||||
devices: {
|
||||
maxHeight: "calc(100vh - 64px)",
|
||||
|
@ -2498,7 +2498,6 @@ const Dark: TTheme = {
|
||||
borderBottom: `1px solid ${grayDarkStrong} !important`,
|
||||
margin: "6px 16px 6px 16px !important",
|
||||
height: "1px !important",
|
||||
width: "calc(100% - 32px) !important",
|
||||
},
|
||||
text: {
|
||||
header: {
|
||||
@ -2519,7 +2518,7 @@ const Dark: TTheme = {
|
||||
background: "none",
|
||||
svgFill: white,
|
||||
header: {
|
||||
height: "49px",
|
||||
height: "55px",
|
||||
borderBottom: `1px solid ${grayDarkStrong}`,
|
||||
marginBottom: "6px",
|
||||
},
|
||||
@ -2529,7 +2528,7 @@ const Dark: TTheme = {
|
||||
padding: "0 12px",
|
||||
mobile: {
|
||||
height: "36px",
|
||||
padding: "0 16px 6px",
|
||||
padding: "6px 16px",
|
||||
},
|
||||
},
|
||||
newContextMenu: {
|
||||
@ -2537,7 +2536,6 @@ const Dark: TTheme = {
|
||||
borderRadius: "6px",
|
||||
mobileBorderRadius: "6px 6px 0 0",
|
||||
boxShadow: `0px 8px 16px 0px ${boxShadowDarkColor}`,
|
||||
padding: "6px 0px",
|
||||
border: `1px solid ${grayDarkStrong}`,
|
||||
devices: {
|
||||
maxHeight: "calc(100vh - 64px)",
|
||||
|
Loading…
Reference in New Issue
Block a user