Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/web/ce/components/navigations/top-navigation-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ export const TopNavigationRoot = observer(() => {

return (
<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,
})}
>
{/* Workspace Menu */}
<div className="flex items-center justify-start flex-shrink-0">
<div className="shrink-0 flex-1">
<WorkspaceMenuRoot />
</div>
{/* Power K Search */}
<div className="flex items-center justify-center flex-grow px-4">
<div className="shrink-0">
<TopNavPowerK />
</div>
{/* 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 />
<div className="flex items-center justify-center size-8 hover:bg-custom-background-80 rounded-md">
<UserMenuRoot size="xs" />
Expand Down
3 changes: 3 additions & 0 deletions apps/web/ce/components/workspace/sidebar/helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DraftIcon,
HomeIcon,
InboxIcon,
MultipleStickyIcon,
ProjectIcon,
ViewsIcon,
YourWorkIcon,
Expand All @@ -31,5 +32,7 @@ export const getSidebarNavigationItemIcon = (key: string, className: string = ""
return <DraftIcon className={cn("size-4 flex-shrink-0", className)} />;
case "archives":
return <ArchiveIcon className={cn("size-4 flex-shrink-0", className)} />;
case "stickies":
return <MultipleStickyIcon className={cn("size-4 flex-shrink-0", className)} />;
}
};
77 changes: 14 additions & 63 deletions apps/web/core/components/navigation/customize-navigation-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
const filteredPersonalItems = PERSONAL_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) => {
// Permission check
const hasPermission = allowPermissions(
Expand All @@ -94,11 +94,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
};
});

// Sort pinned items by sort_order
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 };
return items.sort((a, b) => a.sortOrder - b.sortOrder);
}, [workspaceSlug, allowPermissions, workspacePreferences]);

// Handle checkbox toggle
Expand Down Expand Up @@ -134,7 +130,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
);

// Separate personal items into enabled/disabled
const { enabledPersonalItems, disabledPersonalItems } = useMemo(() => {
const personalItems = useMemo(() => {
const items = filteredPersonalItems.map((item) => {
const itemState = personalPreferences.items[item.key];
const isEnabled = typeof itemState === "boolean" ? itemState : (itemState?.enabled ?? true);
Expand All @@ -147,10 +143,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
};
});

const enabled = items.filter((item) => item.isEnabled).sort((a, b) => a.sortOrder - b.sortOrder);
const disabled = items.filter((item) => !item.isEnabled);

return { enabledPersonalItems: enabled, disabledPersonalItems: disabled };
return items.sort((a, b) => a.sortOrder - b.sortOrder);
}, [personalPreferences, filteredPersonalItems]);

// Prevent typing invalid characters in number input
Expand Down Expand Up @@ -203,18 +196,19 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
{/* Personal Section */}
<div className="flex flex-col gap-2">
<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">
<Sortable
data={enabledPersonalItems}
data={personalItems}
onChange={handlePersonalReorder}
keyExtractor={(item) => item.key}
id="personal-enabled-items"
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">
<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">
{getSidebarNavigationItemIcon(item.key)}
<label className="text-sm text-custom-text-200 flex-1 cursor-pointer">
Expand All @@ -224,27 +218,6 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
</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>

Expand All @@ -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">
{/* Pinned Items - Draggable */}
<Sortable
data={pinnedItems}
data={workspaceItems}
onChange={handleReorder}
keyExtractor={(item) => item.key}
id="workspace-pinned-items"
Expand All @@ -263,7 +236,10 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
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">
<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)} />
<Checkbox
checked={!!workspacePreferences.items[item.key]?.is_pinned}
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>
Expand All @@ -272,31 +248,6 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
);
}}
/>

{/* 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
checked={false}
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>
);
})}
</div>
)}
</div>
</div>

Expand Down
15 changes: 10 additions & 5 deletions apps/web/core/components/navigation/project-actions-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"use client";

import type { FC } from "react";
import React, { useState, useRef } from "react";
import { useState, useRef } from "react";
import { useNavigate } from "react-router";
import { LinkIcon, LogOut, MoreHorizontal, Settings, Share2, ArchiveIcon } from "lucide-react";
// plane imports
import { MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { CustomMenu } from "@plane/ui";

type ProjectActionsMenuProps = {
type Props = {
workspaceSlug: string;
project: {
id: string;
Expand All @@ -20,7 +21,7 @@ type ProjectActionsMenuProps = {
onPublishModal: () => void;
};

export const ProjectActionsMenu: FC<ProjectActionsMenuProps> = ({
export const ProjectActionsMenu: FC<Props> = ({
workspaceSlug,
project,
isAdmin,
Expand All @@ -29,10 +30,14 @@ export const ProjectActionsMenu: FC<ProjectActionsMenuProps> = ({
onLeaveProject,
onPublishModal,
}) => {
// states
const [isMenuActive, setIsMenuActive] = useState(false);
// translation
const { t } = useTranslation();
const navigate = useNavigate();
// refs
const actionSectionRef = useRef<HTMLDivElement | null>(null);
const [isMenuActive, setIsMenuActive] = useState(false);
// router
const navigate = useNavigate();

return (
<CustomMenu
Expand Down
4 changes: 2 additions & 2 deletions apps/web/core/components/navigation/project-header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FC } from "react";
// plane imports
import { Logo } from "@plane/propel/emoji-icon-picker";
import type { TLogoProps } from "@plane/types";
import { cn } from "@plane/utils";

type ProjectHeaderProps = {
project: {
Expand All @@ -11,7 +11,7 @@ type ProjectHeaderProps = {
};

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">
<Logo logo={project.logo_props} size={16} />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import React from "react";
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 { SetAsDefaultIcon } from "@plane/propel/icons";
import { Menu } from "@plane/propel/menu";
import { TabNavigationItem } from "@plane/propel/tab-navigation";
import { Tooltip } from "@plane/propel/tooltip";
import { cn } from "@plane/utils";
// local imports
import type { TNavigationItem } from "./tab-navigation-root";
import type { TTabPreferences } from "./tab-navigation-utils";

export type TTabNavigationOverflowMenuProps = {
type Props = {
overflowItems: TNavigationItem[];
isActive: (item: TNavigationItem) => boolean;
tabPreferences: TTabPreferences;
Expand All @@ -19,9 +22,9 @@ export type TTabNavigationOverflowMenuProps = {
/**
* Overflow menu for tab navigation items
* 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,
isActive,
tabPreferences,
Expand All @@ -48,23 +51,12 @@ export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps
const isDefault = item.key === tabPreferences.defaultTab;

return (
<Menu.MenuItem
key={`${item.key}-overflow-${itemIsActive ? "active" : "inactive"}`}
className={cn("p-0 w-full", {
"bg-custom-background-80": itemIsActive,
})}
>
<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>
<Menu.MenuItem key={`${item.key}-overflow-${itemIsActive ? "active" : "inactive"}`} className="p-0 w-full">
<div className="flex items-center justify-between w-full group/menu-item">
<Link to={item.href} className="flex-1 min-w-0 w-full p-1">
<span className="text-xs">{t(item.i18n_key)}</span>
</Link>
<div
className={cn("flex items-center gap-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity", {
"opacity-100": itemIsActive,
})}
>
<div className="flex items-center">
{/* Show Eye icon ONLY for user-hidden items */}
{isHidden && (
<button
Expand All @@ -74,23 +66,30 @@ export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps
e.preventDefault();
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"
>
<Pin className="h-3.5 w-3.5 text-custom-text-300 rotate-45" />
<Pin className="size-3" />
</button>
)}
<button
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
onToggleDefault(item.key);
}}
className="p-1 rounded hover:bg-custom-background-90"
title={isDefault ? "Clear default" : "Set as default"}
>
<Star className={`h-3.5 w-3.5 text-custom-text-300 ${isDefault ? "fill-current" : ""}`} />
</button>
<Tooltip tooltipContent={isDefault ? "Clear default" : "Set as default"}>
<button
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
onToggleDefault(item.key);
}}
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"}
>
<SetAsDefaultIcon className="size-3" />
</button>
</Tooltip>
</div>
</div>
</Menu.MenuItem>
Expand Down
26 changes: 14 additions & 12 deletions apps/web/core/components/navigation/tab-navigation-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,21 +167,23 @@ export const TabNavigationRoot: FC<TTabNavigationRootProps> = observer((props) =
/>

{/* 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-2 flex-shrink-0 max-w-48 truncate">
<div className="flex items-center gap-3 overflow-hidden pl-1.5 size-full">
<div className="flex items-center gap-2 shrink-0">
<ProjectHeader project={project} />
<ProjectActionsMenu
workspaceSlug={workspaceSlug}
project={project}
isAdmin={isAdmin}
isAuthorized={isAuthorized}
onCopyText={handleCopyText}
onLeaveProject={handleLeaveProject}
onPublishModal={() => handlePublishModal(true)}
/>
<div className="shrink-0">
<ProjectActionsMenu
workspaceSlug={workspaceSlug}
project={project}
isAdmin={isAdmin}
isAuthorized={isAuthorized}
onCopyText={handleCopyText}
onLeaveProject={handleLeaveProject}
onPublishModal={() => handlePublishModal(true)}
/>
</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">
<TabNavigationList className="h-full">
Expand Down
Loading
Loading