UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: локальная привязка окна профиля в верхнем toolbar
This commit is contained in:
@@ -132,7 +132,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 + rect.height / 2;
|
||||
const top = rect.bottom + 10;
|
||||
|
||||
setSidebarSearchPosition({
|
||||
left,
|
||||
@@ -331,7 +331,7 @@ export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
||||
ref={sidebarSearchButtonRef}
|
||||
type="button"
|
||||
className={cn(
|
||||
"absolute left-0 top-0 z-[161] flex size-8 items-center justify-center rounded-full border border-white/8 bg-white/[0.04] text-placeholder backdrop-blur-[18px] outline-none transition-all hover:bg-white/[0.07]"
|
||||
"absolute left-0 top-0 z-[161] flex size-8 items-center justify-center rounded-full border-0 bg-white/[0.04] text-placeholder backdrop-blur-[18px] outline-none transition-all hover:bg-white/[0.07]"
|
||||
)}
|
||||
onClick={() => {
|
||||
if (isOpen) {
|
||||
@@ -374,7 +374,6 @@ export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
||||
left: `${sidebarSearchPosition.left}px`,
|
||||
top: `${sidebarSearchPosition.top}px`,
|
||||
width: `${sidebarSearchPosition.width}px`,
|
||||
transform: "translateY(-50%)",
|
||||
}}
|
||||
>
|
||||
<div className="relative">
|
||||
@@ -400,7 +399,7 @@ export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="nodedc-glass-modal nodedc-glass-popup-surface absolute bottom-full left-0 mb-3 flex max-h-[70vh] w-full flex-col overflow-hidden rounded-[1.5rem] pt-3">
|
||||
<div className="nodedc-glass-modal nodedc-glass-popup-surface absolute top-full left-0 mt-3 flex max-h-[70vh] w-full flex-col overflow-hidden rounded-[1.5rem] pt-3">
|
||||
<div className="px-4 pb-2">
|
||||
<div className="text-[13px] font-medium text-secondary">
|
||||
{t("power_k.search_menu.quick_access_title")}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Menu } from "@headlessui/react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { LogOut, Settings, Settings2 } from "lucide-react";
|
||||
@@ -23,7 +24,7 @@ import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
|
||||
type TUserMenuRootProps = {
|
||||
variant?: "default" | "sidebar-utility";
|
||||
variant?: "default" | "sidebar-utility" | "toolbar";
|
||||
};
|
||||
|
||||
export const UserMenuRoot = observer(function UserMenuRoot(props: TUserMenuRootProps) {
|
||||
@@ -42,6 +43,9 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: TUserMenuRootP
|
||||
// translation
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isSidebarUtilityVariant = variant === "sidebar-utility";
|
||||
const isToolbarVariant = variant === "toolbar";
|
||||
|
||||
const handleSignOut = () => {
|
||||
signOut().catch(() =>
|
||||
setToast({
|
||||
@@ -58,46 +62,8 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: TUserMenuRootP
|
||||
else toggleAnySidebarDropdown(false);
|
||||
}, [isUserMenuOpen, toggleAnySidebarDropdown]);
|
||||
|
||||
return (
|
||||
<CustomMenu
|
||||
className="flex items-center"
|
||||
customButton={
|
||||
variant === "sidebar-utility" ? (
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-8 items-center justify-center rounded-full border border-white/8 bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07]"
|
||||
>
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={18}
|
||||
shape="circle"
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<AppSidebarItem
|
||||
variant="button"
|
||||
item={{
|
||||
icon: (
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={20}
|
||||
shape="circle"
|
||||
/>
|
||||
),
|
||||
isActive: isUserMenuOpen,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
menuButtonOnClick={() => !isUserMenuOpen && setIsUserMenuOpen(true)}
|
||||
onMenuClose={() => setIsUserMenuOpen(false)}
|
||||
placement="bottom-end"
|
||||
maxHeight="2xl"
|
||||
optionsClassName="w-72 p-3 flex flex-col gap-y-3"
|
||||
closeOnSelect
|
||||
>
|
||||
const menuContent = (
|
||||
<>
|
||||
<div className="relative h-29 w-full rounded-lg">
|
||||
<CoverImage
|
||||
src={currentUser?.cover_image_url ?? undefined}
|
||||
@@ -164,6 +130,77 @@ export const UserMenuRoot = observer(function UserMenuRoot(props: TUserMenuRootP
|
||||
{t("enter_god_mode")}
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
if (isToolbarVariant) {
|
||||
return (
|
||||
<Menu as="div" className="relative">
|
||||
<Menu.Button
|
||||
type="button"
|
||||
aria-label={t("profile")}
|
||||
className="flex size-8 items-center justify-center rounded-full border-0 bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07]"
|
||||
>
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={18}
|
||||
shape="circle"
|
||||
/>
|
||||
</Menu.Button>
|
||||
|
||||
<Menu.Items className="absolute top-full left-0 z-[170] mt-2 origin-top-left">
|
||||
<div className="nodedc-glass-modal nodedc-glass-popup-surface flex w-72 flex-col gap-y-3 rounded-[1.25rem] border-0 p-3 shadow-none outline-none">
|
||||
{menuContent}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomMenu
|
||||
className="flex items-center"
|
||||
customButtonClassName={
|
||||
isSidebarUtilityVariant
|
||||
? "flex size-8 items-center justify-center rounded-full border-0 bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07]"
|
||||
: ""
|
||||
}
|
||||
customButton={
|
||||
isSidebarUtilityVariant ? (
|
||||
<span className="pointer-events-none flex size-8 items-center justify-center">
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={18}
|
||||
shape="circle"
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<AppSidebarItem
|
||||
variant="button"
|
||||
item={{
|
||||
icon: (
|
||||
<Avatar
|
||||
name={currentUser?.display_name}
|
||||
src={getFileURL(currentUser?.avatar_url ?? "")}
|
||||
size={20}
|
||||
shape="circle"
|
||||
/>
|
||||
),
|
||||
isActive: isUserMenuOpen,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
menuButtonOnClick={() => !isUserMenuOpen && setIsUserMenuOpen(true)}
|
||||
onMenuClose={() => setIsUserMenuOpen(false)}
|
||||
placement="bottom-end"
|
||||
maxHeight="2xl"
|
||||
optionsClassName="w-72 p-3 flex flex-col gap-y-3"
|
||||
closeOnSelect
|
||||
>
|
||||
{menuContent}
|
||||
</CustomMenu>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ import { WorkspaceLogo } from "../logo";
|
||||
import SidebarDropdownItem from "./dropdown-item";
|
||||
|
||||
type WorkspaceMenuRootProps = {
|
||||
variant: "sidebar" | "top-navigation" | "sidebar-panel";
|
||||
variant: "sidebar" | "top-navigation" | "sidebar-panel" | "toolbar";
|
||||
};
|
||||
|
||||
type WorkspaceMenuStateSyncProps = {
|
||||
@@ -46,7 +46,7 @@ function WorkspaceMenuStateSync(props: WorkspaceMenuStateSyncProps) {
|
||||
const { open, variant, sidebarPanelButtonRef, onSidebarDropdownToggle, onSidebarPanelPositionChange } = props;
|
||||
|
||||
const updateSidebarPanelMenuPosition = useCallback(() => {
|
||||
if (variant !== "sidebar-panel" || !sidebarPanelButtonRef.current || typeof window === "undefined") return;
|
||||
if (!["sidebar-panel", "toolbar"].includes(variant) || !sidebarPanelButtonRef.current || typeof window === "undefined") return;
|
||||
|
||||
const rect = sidebarPanelButtonRef.current.getBoundingClientRect();
|
||||
const width = 480;
|
||||
@@ -64,7 +64,7 @@ function WorkspaceMenuStateSync(props: WorkspaceMenuStateSyncProps) {
|
||||
}, [onSidebarDropdownToggle, open]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!open || variant !== "sidebar-panel") {
|
||||
if (!open || !["sidebar-panel", "toolbar"].includes(variant)) {
|
||||
onSidebarPanelPositionChange(null);
|
||||
return;
|
||||
}
|
||||
@@ -133,6 +133,7 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
"w-full justify-center text-center": variant === "sidebar",
|
||||
"flex-grow justify-stretch text-left": variant === "top-navigation",
|
||||
"w-full max-w-none justify-stretch text-left": variant === "sidebar-panel",
|
||||
"w-fit max-w-none justify-center text-center": variant === "toolbar",
|
||||
})}
|
||||
>
|
||||
{({ open, close }: { open: boolean; close: () => void }) => {
|
||||
@@ -220,6 +221,24 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
/>
|
||||
</Menu.Button>
|
||||
)}
|
||||
{variant === "toolbar" && (
|
||||
<Menu.Button
|
||||
ref={sidebarPanelButtonRef}
|
||||
className={cn(
|
||||
"flex size-8 items-center justify-center rounded-full bg-white/[0.04] backdrop-blur-[18px] transition-all hover:bg-white/[0.07] focus:outline-none",
|
||||
{
|
||||
"bg-white/[0.08]": open,
|
||||
}
|
||||
)}
|
||||
aria-label={t("aria_labels.projects_sidebar.open_workspace_switcher")}
|
||||
>
|
||||
<WorkspaceLogo
|
||||
logo={activeWorkspace?.logo_url}
|
||||
name={activeWorkspace?.name}
|
||||
classNames="size-8 rounded-[0.9rem]"
|
||||
/>
|
||||
</Menu.Button>
|
||||
)}
|
||||
{(() => {
|
||||
const menuItems = (
|
||||
<Menu.Items
|
||||
@@ -228,15 +247,15 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
"z-21 mt-1 flex min-w-[30rem] origin-top-left flex-col divide-y overflow-hidden outline-none",
|
||||
{
|
||||
"fixed divide-subtle rounded-md border-[0.5px] border-strong bg-surface-1 shadow-raised-200":
|
||||
variant !== "sidebar-panel",
|
||||
!["sidebar-panel", "toolbar"].includes(variant),
|
||||
"top-11 left-14": variant === "sidebar",
|
||||
"top-10 left-4": variant === "top-navigation",
|
||||
"nodedc-glass-modal nodedc-glass-popup-surface rounded-[1.5rem] divide-white/10":
|
||||
variant === "sidebar-panel",
|
||||
["sidebar-panel", "toolbar"].includes(variant),
|
||||
}
|
||||
)}
|
||||
style={
|
||||
variant === "sidebar-panel" && sidebarPanelMenuPosition
|
||||
["sidebar-panel", "toolbar"].includes(variant) && sidebarPanelMenuPosition
|
||||
? {
|
||||
position: "fixed",
|
||||
left: `${sidebarPanelMenuPosition.left}px`,
|
||||
@@ -251,8 +270,8 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
className={cn(
|
||||
"sticky top-0 z-21 h-full w-full flex-shrink-0 truncate px-4 pt-3 pb-1 text-left text-13 font-medium text-placeholder",
|
||||
{
|
||||
"rounded-md bg-surface-1": variant !== "sidebar-panel",
|
||||
"bg-transparent": variant === "sidebar-panel",
|
||||
"rounded-md bg-surface-1": !["sidebar-panel", "toolbar"].includes(variant),
|
||||
"bg-transparent": ["sidebar-panel", "toolbar"].includes(variant),
|
||||
}
|
||||
)}
|
||||
>
|
||||
@@ -324,7 +343,7 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||
</Menu.Items>
|
||||
);
|
||||
|
||||
if (variant === "sidebar-panel") {
|
||||
if (["sidebar-panel", "toolbar"].includes(variant)) {
|
||||
if (!open || !sidebarPanelMenuPosition || typeof document === "undefined") return null;
|
||||
return createPortal(menuItems, document.body);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user