[WEB-5511] regression: revamped navigation UI bugs (#8183)
This commit is contained in:
parent
05b1c147a9
commit
eddf80aaed
13 changed files with 164 additions and 149 deletions
|
|
@ -14,20 +14,20 @@ export const TopNavigationRoot = observer(() => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("flex items-center justify-evenly min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
|
className={cn("flex items-center min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
|
||||||
"px-2": !showLabel,
|
"px-2": !showLabel,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{/* Workspace Menu */}
|
{/* Workspace Menu */}
|
||||||
<div className="flex items-center justify-start flex-shrink-0">
|
<div className="shrink-0 flex-1">
|
||||||
<WorkspaceMenuRoot />
|
<WorkspaceMenuRoot />
|
||||||
</div>
|
</div>
|
||||||
{/* Power K Search */}
|
{/* Power K Search */}
|
||||||
<div className="flex items-center justify-center flex-grow px-4">
|
<div className="shrink-0">
|
||||||
<TopNavPowerK />
|
<TopNavPowerK />
|
||||||
</div>
|
</div>
|
||||||
{/* Additional Actions */}
|
{/* Additional Actions */}
|
||||||
<div className="flex gap-1 items-center justify-end flex-shrink-0 min-w-48">
|
<div className="shrink-0 flex-1 flex gap-1 items-center justify-end">
|
||||||
<HelpMenuRoot />
|
<HelpMenuRoot />
|
||||||
<div className="flex items-center justify-center size-8 hover:bg-custom-background-80 rounded-md">
|
<div className="flex items-center justify-center size-8 hover:bg-custom-background-80 rounded-md">
|
||||||
<UserMenuRoot size="xs" />
|
<UserMenuRoot size="xs" />
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
DraftIcon,
|
DraftIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
InboxIcon,
|
InboxIcon,
|
||||||
|
MultipleStickyIcon,
|
||||||
ProjectIcon,
|
ProjectIcon,
|
||||||
ViewsIcon,
|
ViewsIcon,
|
||||||
YourWorkIcon,
|
YourWorkIcon,
|
||||||
|
|
@ -31,5 +32,7 @@ export const getSidebarNavigationItemIcon = (key: string, className: string = ""
|
||||||
return <DraftIcon className={cn("size-4 flex-shrink-0", className)} />;
|
return <DraftIcon className={cn("size-4 flex-shrink-0", className)} />;
|
||||||
case "archives":
|
case "archives":
|
||||||
return <ArchiveIcon className={cn("size-4 flex-shrink-0", className)} />;
|
return <ArchiveIcon className={cn("size-4 flex-shrink-0", className)} />;
|
||||||
|
case "stickies":
|
||||||
|
return <MultipleStickyIcon className={cn("size-4 flex-shrink-0", className)} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
const filteredPersonalItems = PERSONAL_ITEMS;
|
const filteredPersonalItems = PERSONAL_ITEMS;
|
||||||
|
|
||||||
// Filter workspace items by permissions and feature flags, then get pinned/unpinned items
|
// Filter workspace items by permissions and feature flags, then get pinned/unpinned items
|
||||||
const { pinnedItems, unpinnedItems } = useMemo(() => {
|
const workspaceItems = useMemo(() => {
|
||||||
const items = WORKSPACE_SIDEBAR_DYNAMIC_NAVIGATION_ITEMS_LINKS.filter((item) => {
|
const items = WORKSPACE_SIDEBAR_DYNAMIC_NAVIGATION_ITEMS_LINKS.filter((item) => {
|
||||||
// Permission check
|
// Permission check
|
||||||
const hasPermission = allowPermissions(
|
const hasPermission = allowPermissions(
|
||||||
|
|
@ -94,11 +94,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort pinned items by sort_order
|
return items.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
const pinned = items.filter((item) => item.isPinned).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
||||||
const unpinned = items.filter((item) => !item.isPinned);
|
|
||||||
|
|
||||||
return { pinnedItems: pinned, unpinnedItems: unpinned };
|
|
||||||
}, [workspaceSlug, allowPermissions, workspacePreferences]);
|
}, [workspaceSlug, allowPermissions, workspacePreferences]);
|
||||||
|
|
||||||
// Handle checkbox toggle
|
// Handle checkbox toggle
|
||||||
|
|
@ -134,7 +130,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
);
|
);
|
||||||
|
|
||||||
// Separate personal items into enabled/disabled
|
// Separate personal items into enabled/disabled
|
||||||
const { enabledPersonalItems, disabledPersonalItems } = useMemo(() => {
|
const personalItems = useMemo(() => {
|
||||||
const items = filteredPersonalItems.map((item) => {
|
const items = filteredPersonalItems.map((item) => {
|
||||||
const itemState = personalPreferences.items[item.key];
|
const itemState = personalPreferences.items[item.key];
|
||||||
const isEnabled = typeof itemState === "boolean" ? itemState : (itemState?.enabled ?? true);
|
const isEnabled = typeof itemState === "boolean" ? itemState : (itemState?.enabled ?? true);
|
||||||
|
|
@ -147,10 +143,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const enabled = items.filter((item) => item.isEnabled).sort((a, b) => a.sortOrder - b.sortOrder);
|
return items.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
const disabled = items.filter((item) => !item.isEnabled);
|
|
||||||
|
|
||||||
return { enabledPersonalItems: enabled, disabledPersonalItems: disabled };
|
|
||||||
}, [personalPreferences, filteredPersonalItems]);
|
}, [personalPreferences, filteredPersonalItems]);
|
||||||
|
|
||||||
// Prevent typing invalid characters in number input
|
// Prevent typing invalid characters in number input
|
||||||
|
|
@ -203,18 +196,19 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
{/* Personal Section */}
|
{/* Personal Section */}
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h3 className="text-sm font-semibold text-custom-text-400">{t("personal")}</h3>
|
<h3 className="text-sm font-semibold text-custom-text-400">{t("personal")}</h3>
|
||||||
|
|
||||||
{/* Enabled Items - Sortable */}
|
|
||||||
<div className="border border-custom-border-200 rounded-md py-2 bg-custom-background-90">
|
<div className="border border-custom-border-200 rounded-md py-2 bg-custom-background-90">
|
||||||
<Sortable
|
<Sortable
|
||||||
data={enabledPersonalItems}
|
data={personalItems}
|
||||||
onChange={handlePersonalReorder}
|
onChange={handlePersonalReorder}
|
||||||
keyExtractor={(item) => item.key}
|
keyExtractor={(item) => item.key}
|
||||||
id="personal-enabled-items"
|
id="personal-enabled-items"
|
||||||
render={(item) => (
|
render={(item) => (
|
||||||
<div className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 transition-all duration-200">
|
<div className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 transition-all duration-200">
|
||||||
<GripVertical className="size-4 text-custom-text-400 cursor-grab active:cursor-grabbing transition-colors" />
|
<GripVertical className="size-4 text-custom-text-400 cursor-grab active:cursor-grabbing transition-colors" />
|
||||||
<Checkbox checked onChange={(e) => togglePersonalItem(item.key, e.target.checked)} />
|
<Checkbox
|
||||||
|
checked={!!personalPreferences.items[item.key]?.enabled}
|
||||||
|
onChange={(e) => togglePersonalItem(item.key, e.target.checked)}
|
||||||
|
/>
|
||||||
<div className="flex items-center gap-2 flex-1">
|
<div className="flex items-center gap-2 flex-1">
|
||||||
{getSidebarNavigationItemIcon(item.key)}
|
{getSidebarNavigationItemIcon(item.key)}
|
||||||
<label className="text-sm text-custom-text-200 flex-1 cursor-pointer">
|
<label className="text-sm text-custom-text-200 flex-1 cursor-pointer">
|
||||||
|
|
@ -224,27 +218,6 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Disabled Items */}
|
|
||||||
{disabledPersonalItems.length > 0 && (
|
|
||||||
<div className={cn("space-y-1", enabledPersonalItems.length > 0 && "mt-1")}>
|
|
||||||
{disabledPersonalItems.map((item) => (
|
|
||||||
<div
|
|
||||||
key={item.key}
|
|
||||||
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 transition-all duration-200"
|
|
||||||
>
|
|
||||||
<GripVertical className="size-4 text-custom-text-400 opacity-40" />
|
|
||||||
<Checkbox checked={false} onChange={(e) => togglePersonalItem(item.key, e.target.checked)} />
|
|
||||||
<div className="flex items-center gap-2 flex-1">
|
|
||||||
{getSidebarNavigationItemIcon(item.key)}
|
|
||||||
<label className="text-sm text-custom-text-200 flex-1 cursor-pointer">
|
|
||||||
{t(item.labelTranslationKey)}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -254,7 +227,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
<div className="border border-custom-border-200 rounded-md py-2 bg-custom-background-90">
|
<div className="border border-custom-border-200 rounded-md py-2 bg-custom-background-90">
|
||||||
{/* Pinned Items - Draggable */}
|
{/* Pinned Items - Draggable */}
|
||||||
<Sortable
|
<Sortable
|
||||||
data={pinnedItems}
|
data={workspaceItems}
|
||||||
onChange={handleReorder}
|
onChange={handleReorder}
|
||||||
keyExtractor={(item) => item.key}
|
keyExtractor={(item) => item.key}
|
||||||
id="workspace-pinned-items"
|
id="workspace-pinned-items"
|
||||||
|
|
@ -263,29 +236,8 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 group transition-all duration-200">
|
<div className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 group transition-all duration-200">
|
||||||
<GripVertical className="size-4 text-custom-text-400 cursor-grab active:cursor-grabbing transition-colors" />
|
<GripVertical className="size-4 text-custom-text-400 cursor-grab active:cursor-grabbing transition-colors" />
|
||||||
<Checkbox checked onChange={(e) => handleWorkspaceItemToggle(item.key, e.target.checked)} />
|
|
||||||
<div className="flex items-center gap-2 flex-1">
|
|
||||||
{icon}
|
|
||||||
<span className="text-sm text-custom-text-200">{t(item.labelTranslationKey)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Unpinned Items */}
|
|
||||||
{unpinnedItems.length > 0 && (
|
|
||||||
<div className="space-y-1 mt-1">
|
|
||||||
{unpinnedItems.map((item) => {
|
|
||||||
const icon = getSidebarNavigationItemIcon(item.key);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={item.key}
|
|
||||||
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 transition-all duration-200"
|
|
||||||
>
|
|
||||||
<GripVertical className="size-4 text-custom-text-400 opacity-40" />
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={false}
|
checked={!!workspacePreferences.items[item.key]?.is_pinned}
|
||||||
onChange={(e) => handleWorkspaceItemToggle(item.key, e.target.checked)}
|
onChange={(e) => handleWorkspaceItemToggle(item.key, e.target.checked)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2 flex-1">
|
<div className="flex items-center gap-2 flex-1">
|
||||||
|
|
@ -294,9 +246,8 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
}}
|
||||||
</div>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import React, { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { LinkIcon, LogOut, MoreHorizontal, Settings, Share2, ArchiveIcon } from "lucide-react";
|
import { LinkIcon, LogOut, MoreHorizontal, Settings, Share2, ArchiveIcon } from "lucide-react";
|
||||||
|
// plane imports
|
||||||
import { MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
import { MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { CustomMenu } from "@plane/ui";
|
import { CustomMenu } from "@plane/ui";
|
||||||
|
|
||||||
type ProjectActionsMenuProps = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
project: {
|
project: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -20,7 +21,7 @@ type ProjectActionsMenuProps = {
|
||||||
onPublishModal: () => void;
|
onPublishModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectActionsMenu: FC<ProjectActionsMenuProps> = ({
|
export const ProjectActionsMenu: FC<Props> = ({
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
project,
|
project,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
|
|
@ -29,10 +30,14 @@ export const ProjectActionsMenu: FC<ProjectActionsMenuProps> = ({
|
||||||
onLeaveProject,
|
onLeaveProject,
|
||||||
onPublishModal,
|
onPublishModal,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
// states
|
||||||
const navigate = useNavigate();
|
|
||||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||||
|
// translation
|
||||||
|
const { t } = useTranslation();
|
||||||
|
// refs
|
||||||
|
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
// router
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
|
// plane imports
|
||||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||||
import type { TLogoProps } from "@plane/types";
|
import type { TLogoProps } from "@plane/types";
|
||||||
import { cn } from "@plane/utils";
|
|
||||||
|
|
||||||
type ProjectHeaderProps = {
|
type ProjectHeaderProps = {
|
||||||
project: {
|
project: {
|
||||||
|
|
@ -11,7 +11,7 @@ type ProjectHeaderProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectHeader: FC<ProjectHeaderProps> = ({ project }) => (
|
export const ProjectHeader: FC<ProjectHeaderProps> = ({ project }) => (
|
||||||
<div className={cn("flex-grow flex items-center gap-1.5 text-left select-none w-full flex-shrink-0")}>
|
<div className="flex items-center gap-1.5 text-left select-none w-full">
|
||||||
<div className="size-7 rounded-md bg-custom-background-90 flex items-center justify-center flex-shrink-0">
|
<div className="size-7 rounded-md bg-custom-background-90 flex items-center justify-center flex-shrink-0">
|
||||||
<Logo logo={project.logo_props} size={16} />
|
<Logo logo={project.logo_props} size={16} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { MoreHorizontal, Star, Pin } from "lucide-react";
|
import { MoreHorizontal, Pin } from "lucide-react";
|
||||||
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
import { SetAsDefaultIcon } from "@plane/propel/icons";
|
||||||
import { Menu } from "@plane/propel/menu";
|
import { Menu } from "@plane/propel/menu";
|
||||||
import { TabNavigationItem } from "@plane/propel/tab-navigation";
|
import { Tooltip } from "@plane/propel/tooltip";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
|
// local imports
|
||||||
import type { TNavigationItem } from "./tab-navigation-root";
|
import type { TNavigationItem } from "./tab-navigation-root";
|
||||||
import type { TTabPreferences } from "./tab-navigation-utils";
|
import type { TTabPreferences } from "./tab-navigation-utils";
|
||||||
|
|
||||||
export type TTabNavigationOverflowMenuProps = {
|
type Props = {
|
||||||
overflowItems: TNavigationItem[];
|
overflowItems: TNavigationItem[];
|
||||||
isActive: (item: TNavigationItem) => boolean;
|
isActive: (item: TNavigationItem) => boolean;
|
||||||
tabPreferences: TTabPreferences;
|
tabPreferences: TTabPreferences;
|
||||||
|
|
@ -19,9 +22,9 @@ export type TTabNavigationOverflowMenuProps = {
|
||||||
/**
|
/**
|
||||||
* Overflow menu for tab navigation items
|
* Overflow menu for tab navigation items
|
||||||
* Displays items that don't fit in the visible area, with action icons
|
* Displays items that don't fit in the visible area, with action icons
|
||||||
* Shows "Eye" icon for user-hidden items, "Star" icon for all items
|
* Shows "Eye" icon for user-hidden items, "Set as default" icon for all items
|
||||||
*/
|
*/
|
||||||
export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps> = ({
|
export const TabNavigationOverflowMenu: React.FC<Props> = ({
|
||||||
overflowItems,
|
overflowItems,
|
||||||
isActive,
|
isActive,
|
||||||
tabPreferences,
|
tabPreferences,
|
||||||
|
|
@ -48,23 +51,12 @@ export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps
|
||||||
const isDefault = item.key === tabPreferences.defaultTab;
|
const isDefault = item.key === tabPreferences.defaultTab;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem key={`${item.key}-overflow-${itemIsActive ? "active" : "inactive"}`} className="p-0 w-full">
|
||||||
key={`${item.key}-overflow-${itemIsActive ? "active" : "inactive"}`}
|
<div className="flex items-center justify-between w-full group/menu-item">
|
||||||
className={cn("p-0 w-full", {
|
<Link to={item.href} className="flex-1 min-w-0 w-full p-1">
|
||||||
"bg-custom-background-80": itemIsActive,
|
<span className="text-xs">{t(item.i18n_key)}</span>
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between w-full group">
|
|
||||||
<Link to={item.href} className="flex-1 min-w-0 w-full">
|
|
||||||
<TabNavigationItem isActive={itemIsActive}>
|
|
||||||
<span className="text-sm">{t(item.i18n_key)}</span>
|
|
||||||
</TabNavigationItem>
|
|
||||||
</Link>
|
</Link>
|
||||||
<div
|
<div className="flex items-center">
|
||||||
className={cn("flex items-center gap-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity", {
|
|
||||||
"opacity-100": itemIsActive,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{/* Show Eye icon ONLY for user-hidden items */}
|
{/* Show Eye icon ONLY for user-hidden items */}
|
||||||
{isHidden && (
|
{isHidden && (
|
||||||
<button
|
<button
|
||||||
|
|
@ -74,23 +66,30 @@ export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onShow(item.key);
|
onShow(item.key);
|
||||||
}}
|
}}
|
||||||
className="p-1 rounded hover:bg-custom-background-90"
|
className="invisible group-hover/menu-item:visible p-1 rounded text-custom-text-300 hover:text-custom-text-100 transition-colors"
|
||||||
title="Show"
|
title="Show"
|
||||||
>
|
>
|
||||||
<Pin className="h-3.5 w-3.5 text-custom-text-300 rotate-45" />
|
<Pin className="size-3" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
<Tooltip tooltipContent={isDefault ? "Clear default" : "Set as default"}>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onToggleDefault(item.key);
|
onToggleDefault(item.key);
|
||||||
}}
|
}}
|
||||||
className="p-1 rounded hover:bg-custom-background-90"
|
className={cn(
|
||||||
|
"invisible group-hover/menu-item:visible p-1 rounded text-custom-text-300 hover:text-custom-text-100 transition-colors",
|
||||||
|
{
|
||||||
|
visible: isDefault,
|
||||||
|
}
|
||||||
|
)}
|
||||||
title={isDefault ? "Clear default" : "Set as default"}
|
title={isDefault ? "Clear default" : "Set as default"}
|
||||||
>
|
>
|
||||||
<Star className={`h-3.5 w-3.5 text-custom-text-300 ${isDefault ? "fill-current" : ""}`} />
|
<SetAsDefaultIcon className="size-3" />
|
||||||
</button>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Menu.MenuItem>
|
</Menu.MenuItem>
|
||||||
|
|
|
||||||
|
|
@ -167,9 +167,10 @@ export const TabNavigationRoot: FC<TTabNavigationRootProps> = observer((props) =
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* container for the tab navigation */}
|
{/* container for the tab navigation */}
|
||||||
<div className="flex items-center gap-3 overflow-hidden pl-1.5 w-full h-full">
|
<div className="flex items-center gap-3 overflow-hidden pl-1.5 size-full">
|
||||||
<div className="flex items-center gap-2 flex-shrink-0 max-w-48 truncate">
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
<ProjectHeader project={project} />
|
<ProjectHeader project={project} />
|
||||||
|
<div className="shrink-0">
|
||||||
<ProjectActionsMenu
|
<ProjectActionsMenu
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
project={project}
|
project={project}
|
||||||
|
|
@ -180,8 +181,9 @@ export const TabNavigationRoot: FC<TTabNavigationRootProps> = observer((props) =
|
||||||
onPublishModal={() => handlePublishModal(true)}
|
onPublishModal={() => handlePublishModal(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex-shrink-0 h-5 w-1 border-l border-custom-border-200" />
|
<div className="shrink-0 h-5 w-1 border-l border-custom-border-200" />
|
||||||
|
|
||||||
<div ref={containerRef} className="flex items-center h-full flex-1 min-w-0 overflow-hidden">
|
<div ref={containerRef} className="flex items-center h-full flex-1 min-w-0 overflow-hidden">
|
||||||
<TabNavigationList className="h-full">
|
<TabNavigationList className="h-full">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
|
import { PinOff } from "lucide-react";
|
||||||
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { ContextMenu } from "@plane/propel/context-menu";
|
import { ContextMenu } from "@plane/propel/context-menu";
|
||||||
|
import { SetAsDefaultIcon } from "@plane/propel/icons";
|
||||||
import { TabNavigationItem } from "@plane/propel/tab-navigation";
|
import { TabNavigationItem } from "@plane/propel/tab-navigation";
|
||||||
|
// local imports
|
||||||
import type { TNavigationItem } from "./tab-navigation-root";
|
import type { TNavigationItem } from "./tab-navigation-root";
|
||||||
import type { TTabPreferences } from "./tab-navigation-utils";
|
import type { TTabPreferences } from "./tab-navigation-utils";
|
||||||
|
|
||||||
|
|
@ -51,7 +55,9 @@ export const TabNavigationVisibleItem: React.FC<TTabNavigationVisibleItemProps>
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onToggleDefault(item.key);
|
onToggleDefault(item.key);
|
||||||
}}
|
}}
|
||||||
|
className="flex items-center gap-2 text-custom-text-200 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
|
<SetAsDefaultIcon className="shrink-0 size-3" />
|
||||||
<span className="text-xs">{isDefault ? "Clear default" : "Set as default"}</span>
|
<span className="text-xs">{isDefault ? "Clear default" : "Set as default"}</span>
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
<ContextMenu.Item
|
<ContextMenu.Item
|
||||||
|
|
@ -59,7 +65,9 @@ export const TabNavigationVisibleItem: React.FC<TTabNavigationVisibleItemProps>
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onHide(item.key);
|
onHide(item.key);
|
||||||
}}
|
}}
|
||||||
|
className="flex items-center gap-2 text-custom-text-200 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
|
<PinOff className="shrink-0 size-3" />
|
||||||
<span className="text-xs">Hide in more menu</span>
|
<span className="text-xs">Hide in more menu</span>
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
|
|
|
||||||
|
|
@ -207,19 +207,21 @@ export const TopNavPowerK = observer(() => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="relative flex justify-center">
|
<div ref={containerRef} className="relative">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn("relative w-[364px] flex items-center transition-all duration-300 ease-in-out z-30", {
|
||||||
"relative flex items-center transition-all duration-300 ease-in-out z-30",
|
"w-[554px]": isOpen,
|
||||||
isOpen ? "w-[554px]" : "w-[364px]"
|
})}
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center w-full h-7 px-2 py-2 rounded-md bg-custom-sidebar-background-80 hover:bg-custom-background-80 transition-colors duration-200",
|
"flex items-center w-full h-7 p-2 rounded-md bg-custom-sidebar-background-80 hover:bg-custom-background-80 border border-transparent transition-colors duration-200",
|
||||||
isOpen && "border border-custom-border-200"
|
{
|
||||||
|
"border-custom-border-200": isOpen,
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
onClick={() => inputRef.current?.focus()}
|
onClick={() => inputRef.current?.focus()}
|
||||||
|
role="button"
|
||||||
>
|
>
|
||||||
<SearchIcon className="shrink-0 size-3.5 text-custom-text-350 mr-2" />
|
<SearchIcon className="shrink-0 size-3.5 text-custom-text-350 mr-2" />
|
||||||
<input
|
<input
|
||||||
|
|
@ -239,7 +241,6 @@ export const TopNavPowerK = observer(() => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute -top-[6px] left-1/2 -translate-x-1/2 bg-custom-background-100 border border-custom-border-200 rounded-md shadow-lg overflow-hidden z-20 transition-all duration-300 ease-in-out flex flex-col px-0 pt-10",
|
"absolute -top-[6px] left-1/2 -translate-x-1/2 bg-custom-background-100 border border-custom-border-200 rounded-md shadow-lg overflow-hidden z-20 transition-all duration-300 ease-in-out flex flex-col px-0 pt-10",
|
||||||
|
|
|
||||||
|
|
@ -57,19 +57,10 @@ export const useTabPreferences = (workspaceSlug: string, projectId: string): TTa
|
||||||
const updatePreferences = async (newPreferences: TTabPreferences) => {
|
const updatePreferences = async (newPreferences: TTabPreferences) => {
|
||||||
if (!memberId) return;
|
if (!memberId) return;
|
||||||
|
|
||||||
try {
|
|
||||||
await updateProjectMemberPreferences(workspaceSlug, projectId, memberId, {
|
await updateProjectMemberPreferences(workspaceSlug, projectId, memberId, {
|
||||||
default_tab: newPreferences.defaultTab,
|
default_tab: newPreferences.defaultTab,
|
||||||
hide_in_more_menu: newPreferences.hiddenTabs,
|
hide_in_more_menu: newPreferences.hiddenTabs,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating tab preferences:", error);
|
|
||||||
setToast({
|
|
||||||
type: TOAST_TYPE.ERROR,
|
|
||||||
title: "Error!",
|
|
||||||
message: "Something went wrong. Please try again later.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,7 +70,21 @@ export const useTabPreferences = (workspaceSlug: string, projectId: string): TTa
|
||||||
const handleToggleDefaultTab = (tabKey: string) => {
|
const handleToggleDefaultTab = (tabKey: string) => {
|
||||||
const newDefaultTab = tabKey === tabPreferences.defaultTab ? DEFAULT_TAB_KEY : tabKey;
|
const newDefaultTab = tabKey === tabPreferences.defaultTab ? DEFAULT_TAB_KEY : tabKey;
|
||||||
const newPreferences = { ...tabPreferences, defaultTab: newDefaultTab };
|
const newPreferences = { ...tabPreferences, defaultTab: newDefaultTab };
|
||||||
updatePreferences(newPreferences);
|
updatePreferences(newPreferences)
|
||||||
|
.then(() => {
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.SUCCESS,
|
||||||
|
title: "Success!",
|
||||||
|
message: "Default tab updated successfully.",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: "Error!",
|
||||||
|
message: "Failed to update default tab. Please try again later.",
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,7 +95,16 @@ export const useTabPreferences = (workspaceSlug: string, projectId: string): TTa
|
||||||
...tabPreferences,
|
...tabPreferences,
|
||||||
hiddenTabs: [...tabPreferences.hiddenTabs, tabKey],
|
hiddenTabs: [...tabPreferences.hiddenTabs, tabKey],
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
updatePreferences(newPreferences);
|
updatePreferences(newPreferences);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error hiding tab:", error);
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: "Error!",
|
||||||
|
message: "Failed to hide tab. Please try again later.",
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -101,7 +115,16 @@ export const useTabPreferences = (workspaceSlug: string, projectId: string): TTa
|
||||||
...tabPreferences,
|
...tabPreferences,
|
||||||
hiddenTabs: tabPreferences.hiddenTabs.filter((key) => key !== tabKey),
|
hiddenTabs: tabPreferences.hiddenTabs.filter((key) => key !== tabKey),
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
updatePreferences(newPreferences);
|
updatePreferences(newPreferences);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error showing tab:", error);
|
||||||
|
setToast({
|
||||||
|
type: TOAST_TYPE.ERROR,
|
||||||
|
title: "Error!",
|
||||||
|
message: "Something went wrong. Please try again later.",
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -65,14 +65,13 @@ export const WorkspaceMenuRoot = observer(function WorkspaceMenuRoot(props: Work
|
||||||
|
|
||||||
// Toggle sidebar dropdown state when either menu is open
|
// Toggle sidebar dropdown state when either menu is open
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isWorkspaceMenuOpen) toggleAnySidebarDropdown(true);
|
toggleAnySidebarDropdown(isWorkspaceMenuOpen);
|
||||||
else toggleAnySidebarDropdown(false);
|
}, [isWorkspaceMenuOpen, toggleAnySidebarDropdown]);
|
||||||
}, [isWorkspaceMenuOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
as="div"
|
as="div"
|
||||||
className={cn("relative h-full flex max-w-48 truncate", {
|
className={cn("relative h-full flex max-w-48 w-fit whitespace-nowrap truncate", {
|
||||||
"justify-center text-center": renderLogoOnly,
|
"justify-center text-center": renderLogoOnly,
|
||||||
"flex-grow justify-stretch text-left truncate": !renderLogoOnly,
|
"flex-grow justify-stretch text-left truncate": !renderLogoOnly,
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ export * from "./properties";
|
||||||
export * from "./related-icon";
|
export * from "./related-icon";
|
||||||
export * from "./sans-serif-icon";
|
export * from "./sans-serif-icon";
|
||||||
export * from "./serif-icon";
|
export * from "./serif-icon";
|
||||||
|
export * from "./set-as-default-icon";
|
||||||
export * from "./side-panel-icon";
|
export * from "./side-panel-icon";
|
||||||
export * from "./state";
|
export * from "./state";
|
||||||
export * from "./sticky-note-icon";
|
export * from "./sticky-note-icon";
|
||||||
|
|
|
||||||
23
packages/propel/src/icons/set-as-default-icon.tsx
Normal file
23
packages/propel/src/icons/set-as-default-icon.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import type { ISvgIcons } from "./type";
|
||||||
|
|
||||||
|
export function SetAsDefaultIcon({ className = "text-current", ...rest }: ISvgIcons) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="15"
|
||||||
|
height="15"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={className}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7.29167 0.625V13.9583M12.0057 2.57762L2.57762 12.0057M13.9583 7.29167H0.625M12.0057 12.0057L2.57762 2.57762"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.25"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue