UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: фикс popup рабочего пространства, sidebar-search и полировка модалки внешнего запроса
This commit is contained in:
@@ -11,7 +11,7 @@ import { useTranslation } from "@plane/i18n";
|
||||
import type { TIssue } from "@plane/types";
|
||||
import { Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { getTabIndex } from "@plane/utils";
|
||||
import { cn, getTabIndex } from "@plane/utils";
|
||||
// hooks
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
|
||||
@@ -19,10 +19,11 @@ type TInboxIssueTitle = {
|
||||
data: Partial<TIssue>;
|
||||
handleData: (issueKey: keyof Partial<TIssue>, issueValue: Partial<TIssue>[keyof Partial<TIssue>]) => void;
|
||||
isTitleLengthMoreThan255Character?: boolean;
|
||||
inputClassName?: string;
|
||||
};
|
||||
|
||||
export const InboxIssueTitle = observer(function InboxIssueTitle(props: TInboxIssueTitle) {
|
||||
const { data, handleData, isTitleLengthMoreThan255Character } = props;
|
||||
const { data, handleData, isTitleLengthMoreThan255Character, inputClassName } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
@@ -37,7 +38,7 @@ export const InboxIssueTitle = observer(function InboxIssueTitle(props: TInboxIs
|
||||
value={data?.name}
|
||||
onChange={(e) => handleData("name", e.target.value)}
|
||||
placeholder={t("title")}
|
||||
className="w-full text-14"
|
||||
className={cn("w-full text-14", inputClassName)}
|
||||
tabIndex={getIndex("name")}
|
||||
required
|
||||
/>
|
||||
|
||||
@@ -130,7 +130,7 @@ export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
||||
const width = 320;
|
||||
const viewportPadding = 16;
|
||||
const left = Math.min(rect.left, window.innerWidth - width - viewportPadding);
|
||||
const top = rect.top;
|
||||
const top = rect.top + rect.height / 2;
|
||||
|
||||
setSidebarSearchPosition({
|
||||
left,
|
||||
@@ -372,6 +372,7 @@ export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
||||
left: `${sidebarSearchPosition.left}px`,
|
||||
top: `${sidebarSearchPosition.top}px`,
|
||||
width: `${sidebarSearchPosition.width}px`,
|
||||
transform: "translateY(-50%)",
|
||||
}}
|
||||
>
|
||||
<div className="relative">
|
||||
|
||||
@@ -38,7 +38,7 @@ export function ShortcutsModal(props: Props) {
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-30" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
as="div"
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -52,7 +52,7 @@ export function ShortcutsModal(props: Props) {
|
||||
<div className="fixed inset-0 z-30 overflow-y-auto">
|
||||
<div className="my-10 flex items-center justify-center p-4 text-center sm:p-0 md:my-20">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
as="div"
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
|
||||
@@ -121,7 +121,7 @@ export const ProjectsAppPowerKModalWrapper = observer(function ProjectsAppPowerK
|
||||
<Dialog as="div" className="relative z-50" onClose={onClose}>
|
||||
{/* Backdrop */}
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
as="div"
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
@@ -135,7 +135,7 @@ export const ProjectsAppPowerKModalWrapper = observer(function ProjectsAppPowerK
|
||||
<div className="fixed inset-0 z-30 overflow-y-auto">
|
||||
<div className="flex items-center justify-center p-4 sm:p-6 md:p-20">
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
as="div"
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { Fragment, useState, useEffect, useCallback, useLayoutEffect, useRef } from "react";
|
||||
import { Fragment, useEffect, useCallback, useLayoutEffect, useRef, useState, type RefObject } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { createPortal } from "react-dom";
|
||||
@@ -34,6 +34,56 @@ type WorkspaceMenuRootProps = {
|
||||
variant: "sidebar" | "top-navigation" | "sidebar-panel";
|
||||
};
|
||||
|
||||
type WorkspaceMenuStateSyncProps = {
|
||||
open: boolean;
|
||||
variant: WorkspaceMenuRootProps["variant"];
|
||||
sidebarPanelButtonRef: RefObject<HTMLButtonElement | null>;
|
||||
onSidebarDropdownToggle: (value: boolean) => void;
|
||||
onSidebarPanelPositionChange: (position: { left: number; top: number; width: number } | null) => void;
|
||||
};
|
||||
|
||||
function WorkspaceMenuStateSync(props: WorkspaceMenuStateSyncProps) {
|
||||
const { open, variant, sidebarPanelButtonRef, onSidebarDropdownToggle, onSidebarPanelPositionChange } = props;
|
||||
|
||||
const updateSidebarPanelMenuPosition = useCallback(() => {
|
||||
if (variant !== "sidebar-panel" || !sidebarPanelButtonRef.current || typeof window === "undefined") return;
|
||||
|
||||
const rect = sidebarPanelButtonRef.current.getBoundingClientRect();
|
||||
const width = 480;
|
||||
const viewportPadding = 16;
|
||||
|
||||
onSidebarPanelPositionChange({
|
||||
left: Math.min(rect.left, window.innerWidth - width - viewportPadding),
|
||||
top: rect.bottom + 8,
|
||||
width,
|
||||
});
|
||||
}, [onSidebarPanelPositionChange, sidebarPanelButtonRef, variant]);
|
||||
|
||||
useEffect(() => {
|
||||
onSidebarDropdownToggle(open);
|
||||
}, [onSidebarDropdownToggle, open]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!open || variant !== "sidebar-panel") {
|
||||
onSidebarPanelPositionChange(null);
|
||||
return;
|
||||
}
|
||||
|
||||
updateSidebarPanelMenuPosition();
|
||||
|
||||
const handlePositionUpdate = () => updateSidebarPanelMenuPosition();
|
||||
window.addEventListener("resize", handlePositionUpdate);
|
||||
window.addEventListener("scroll", handlePositionUpdate, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handlePositionUpdate);
|
||||
window.removeEventListener("scroll", handlePositionUpdate, true);
|
||||
};
|
||||
}, [onSidebarPanelPositionChange, open, updateSidebarPanelMenuPosition, variant]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: WorkspaceMenuRootProps) {
|
||||
const { variant } = props;
|
||||
// store hooks
|
||||
@@ -48,7 +98,6 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
// translation
|
||||
const { t } = useTranslation();
|
||||
// local state
|
||||
const [isWorkspaceMenuOpen, setIsWorkspaceMenuOpen] = useState(false);
|
||||
const [sidebarPanelMenuPosition, setSidebarPanelMenuPosition] = useState<{
|
||||
left: number;
|
||||
top: number;
|
||||
@@ -77,40 +126,6 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
const workspacesList = orderWorkspacesList(Object.values(workspaces ?? {}));
|
||||
// TODO: fix workspaces list scroll
|
||||
|
||||
const updateSidebarPanelMenuPosition = useCallback(() => {
|
||||
if (variant !== "sidebar-panel" || !sidebarPanelButtonRef.current || typeof window === "undefined") return;
|
||||
|
||||
const rect = sidebarPanelButtonRef.current.getBoundingClientRect();
|
||||
const width = 480;
|
||||
const viewportPadding = 16;
|
||||
|
||||
setSidebarPanelMenuPosition({
|
||||
left: Math.min(rect.left, window.innerWidth - width - viewportPadding),
|
||||
top: rect.bottom + 8,
|
||||
width,
|
||||
});
|
||||
}, [variant]);
|
||||
|
||||
// Toggle sidebar dropdown state when either menu is open
|
||||
useEffect(() => {
|
||||
toggleAnySidebarDropdown(isWorkspaceMenuOpen);
|
||||
}, [isWorkspaceMenuOpen, toggleAnySidebarDropdown]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!isWorkspaceMenuOpen || variant !== "sidebar-panel") return;
|
||||
|
||||
updateSidebarPanelMenuPosition();
|
||||
|
||||
const handlePositionUpdate = () => updateSidebarPanelMenuPosition();
|
||||
window.addEventListener("resize", handlePositionUpdate);
|
||||
window.addEventListener("scroll", handlePositionUpdate, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handlePositionUpdate);
|
||||
window.removeEventListener("scroll", handlePositionUpdate, true);
|
||||
};
|
||||
}, [isWorkspaceMenuOpen, updateSidebarPanelMenuPosition, variant]);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
as="div"
|
||||
@@ -121,13 +136,15 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
})}
|
||||
>
|
||||
{({ open, close }: { open: boolean; close: () => void }) => {
|
||||
// Update local state directly
|
||||
if (isWorkspaceMenuOpen !== open) {
|
||||
setIsWorkspaceMenuOpen(open);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkspaceMenuStateSync
|
||||
open={open}
|
||||
variant={variant}
|
||||
sidebarPanelButtonRef={sidebarPanelButtonRef}
|
||||
onSidebarDropdownToggle={toggleAnySidebarDropdown}
|
||||
onSidebarPanelPositionChange={setSidebarPanelMenuPosition}
|
||||
/>
|
||||
{variant === "sidebar" && (
|
||||
<Menu.Button
|
||||
className={cn("flex size-8 w-full items-center justify-center rounded-md", {
|
||||
|
||||
Reference in New Issue
Block a user