UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: фикс popup рабочего пространства, sidebar-search и полировка модалки внешнего запроса

This commit is contained in:
DCCONSTRUCTIONS
2026-04-19 21:02:35 +03:00
parent 01eb3d4c8a
commit 21581373cd
11 changed files with 278 additions and 147 deletions
@@ -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", {