Skip to content

Commit eddf80a

Browse files
authored
[WEB-5511] regression: revamped navigation UI bugs (#8183)
1 parent 05b1c14 commit eddf80a

File tree

13 files changed

+163
-148
lines changed

13 files changed

+163
-148
lines changed

apps/web/ce/components/navigations/top-navigation-root.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ export const TopNavigationRoot = observer(() => {
1414

1515
return (
1616
<div
17-
className={cn("flex items-center justify-evenly min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
17+
className={cn("flex items-center min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
1818
"px-2": !showLabel,
1919
})}
2020
>
2121
{/* Workspace Menu */}
22-
<div className="flex items-center justify-start flex-shrink-0">
22+
<div className="shrink-0 flex-1">
2323
<WorkspaceMenuRoot />
2424
</div>
2525
{/* Power K Search */}
26-
<div className="flex items-center justify-center flex-grow px-4">
26+
<div className="shrink-0">
2727
<TopNavPowerK />
2828
</div>
2929
{/* Additional Actions */}
30-
<div className="flex gap-1 items-center justify-end flex-shrink-0 min-w-48">
30+
<div className="shrink-0 flex-1 flex gap-1 items-center justify-end">
3131
<HelpMenuRoot />
3232
<div className="flex items-center justify-center size-8 hover:bg-custom-background-80 rounded-md">
3333
<UserMenuRoot size="xs" />

apps/web/ce/components/workspace/sidebar/helper.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
DraftIcon,
66
HomeIcon,
77
InboxIcon,
8+
MultipleStickyIcon,
89
ProjectIcon,
910
ViewsIcon,
1011
YourWorkIcon,
@@ -31,5 +32,7 @@ export const getSidebarNavigationItemIcon = (key: string, className: string = ""
3132
return <DraftIcon className={cn("size-4 flex-shrink-0", className)} />;
3233
case "archives":
3334
return <ArchiveIcon className={cn("size-4 flex-shrink-0", className)} />;
35+
case "stickies":
36+
return <MultipleStickyIcon className={cn("size-4 flex-shrink-0", className)} />;
3437
}
3538
};

apps/web/core/components/navigation/customize-navigation-dialog.tsx

Lines changed: 14 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
7171
const filteredPersonalItems = PERSONAL_ITEMS;
7272

7373
// Filter workspace items by permissions and feature flags, then get pinned/unpinned items
74-
const { pinnedItems, unpinnedItems } = useMemo(() => {
74+
const workspaceItems = useMemo(() => {
7575
const items = WORKSPACE_SIDEBAR_DYNAMIC_NAVIGATION_ITEMS_LINKS.filter((item) => {
7676
// Permission check
7777
const hasPermission = allowPermissions(
@@ -94,11 +94,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
9494
};
9595
});
9696

97-
// Sort pinned items by sort_order
98-
const pinned = items.filter((item) => item.isPinned).sort((a, b) => a.sortOrder - b.sortOrder);
99-
const unpinned = items.filter((item) => !item.isPinned);
100-
101-
return { pinnedItems: pinned, unpinnedItems: unpinned };
97+
return items.sort((a, b) => a.sortOrder - b.sortOrder);
10298
}, [workspaceSlug, allowPermissions, workspacePreferences]);
10399

104100
// Handle checkbox toggle
@@ -134,7 +130,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
134130
);
135131

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

150-
const enabled = items.filter((item) => item.isEnabled).sort((a, b) => a.sortOrder - b.sortOrder);
151-
const disabled = items.filter((item) => !item.isEnabled);
152-
153-
return { enabledPersonalItems: enabled, disabledPersonalItems: disabled };
146+
return items.sort((a, b) => a.sortOrder - b.sortOrder);
154147
}, [personalPreferences, filteredPersonalItems]);
155148

156149
// Prevent typing invalid characters in number input
@@ -203,18 +196,19 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
203196
{/* Personal Section */}
204197
<div className="flex flex-col gap-2">
205198
<h3 className="text-sm font-semibold text-custom-text-400">{t("personal")}</h3>
206-
207-
{/* Enabled Items - Sortable */}
208199
<div className="border border-custom-border-200 rounded-md py-2 bg-custom-background-90">
209200
<Sortable
210-
data={enabledPersonalItems}
201+
data={personalItems}
211202
onChange={handlePersonalReorder}
212203
keyExtractor={(item) => item.key}
213204
id="personal-enabled-items"
214205
render={(item) => (
215206
<div className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 transition-all duration-200">
216207
<GripVertical className="size-4 text-custom-text-400 cursor-grab active:cursor-grabbing transition-colors" />
217-
<Checkbox checked onChange={(e) => togglePersonalItem(item.key, e.target.checked)} />
208+
<Checkbox
209+
checked={!!personalPreferences.items[item.key]?.enabled}
210+
onChange={(e) => togglePersonalItem(item.key, e.target.checked)}
211+
/>
218212
<div className="flex items-center gap-2 flex-1">
219213
{getSidebarNavigationItemIcon(item.key)}
220214
<label className="text-sm text-custom-text-200 flex-1 cursor-pointer">
@@ -224,27 +218,6 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
224218
</div>
225219
)}
226220
/>
227-
228-
{/* Disabled Items */}
229-
{disabledPersonalItems.length > 0 && (
230-
<div className={cn("space-y-1", enabledPersonalItems.length > 0 && "mt-1")}>
231-
{disabledPersonalItems.map((item) => (
232-
<div
233-
key={item.key}
234-
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 transition-all duration-200"
235-
>
236-
<GripVertical className="size-4 text-custom-text-400 opacity-40" />
237-
<Checkbox checked={false} onChange={(e) => togglePersonalItem(item.key, e.target.checked)} />
238-
<div className="flex items-center gap-2 flex-1">
239-
{getSidebarNavigationItemIcon(item.key)}
240-
<label className="text-sm text-custom-text-200 flex-1 cursor-pointer">
241-
{t(item.labelTranslationKey)}
242-
</label>
243-
</div>
244-
</div>
245-
))}
246-
</div>
247-
)}
248221
</div>
249222
</div>
250223

@@ -254,7 +227,7 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
254227
<div className="border border-custom-border-200 rounded-md py-2 bg-custom-background-90">
255228
{/* Pinned Items - Draggable */}
256229
<Sortable
257-
data={pinnedItems}
230+
data={workspaceItems}
258231
onChange={handleReorder}
259232
keyExtractor={(item) => item.key}
260233
id="workspace-pinned-items"
@@ -263,7 +236,10 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
263236
return (
264237
<div className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 group transition-all duration-200">
265238
<GripVertical className="size-4 text-custom-text-400 cursor-grab active:cursor-grabbing transition-colors" />
266-
<Checkbox checked onChange={(e) => handleWorkspaceItemToggle(item.key, e.target.checked)} />
239+
<Checkbox
240+
checked={!!workspacePreferences.items[item.key]?.is_pinned}
241+
onChange={(e) => handleWorkspaceItemToggle(item.key, e.target.checked)}
242+
/>
267243
<div className="flex items-center gap-2 flex-1">
268244
{icon}
269245
<span className="text-sm text-custom-text-200">{t(item.labelTranslationKey)}</span>
@@ -272,31 +248,6 @@ export const CustomizeNavigationDialog: FC<TCustomizeNavigationDialogProps> = ob
272248
);
273249
}}
274250
/>
275-
276-
{/* Unpinned Items */}
277-
{unpinnedItems.length > 0 && (
278-
<div className="space-y-1 mt-1">
279-
{unpinnedItems.map((item) => {
280-
const icon = getSidebarNavigationItemIcon(item.key);
281-
return (
282-
<div
283-
key={item.key}
284-
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-custom-background-90 transition-all duration-200"
285-
>
286-
<GripVertical className="size-4 text-custom-text-400 opacity-40" />
287-
<Checkbox
288-
checked={false}
289-
onChange={(e) => handleWorkspaceItemToggle(item.key, e.target.checked)}
290-
/>
291-
<div className="flex items-center gap-2 flex-1">
292-
{icon}
293-
<span className="text-sm text-custom-text-200">{t(item.labelTranslationKey)}</span>
294-
</div>
295-
</div>
296-
);
297-
})}
298-
</div>
299-
)}
300251
</div>
301252
</div>
302253

apps/web/core/components/navigation/project-actions-menu.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"use client";
22

33
import type { FC } from "react";
4-
import React, { useState, useRef } from "react";
4+
import { useState, useRef } from "react";
55
import { useNavigate } from "react-router";
66
import { LinkIcon, LogOut, MoreHorizontal, Settings, Share2, ArchiveIcon } from "lucide-react";
7+
// plane imports
78
import { MEMBER_TRACKER_ELEMENTS } from "@plane/constants";
89
import { useTranslation } from "@plane/i18n";
910
import { CustomMenu } from "@plane/ui";
1011

11-
type ProjectActionsMenuProps = {
12+
type Props = {
1213
workspaceSlug: string;
1314
project: {
1415
id: string;
@@ -20,7 +21,7 @@ type ProjectActionsMenuProps = {
2021
onPublishModal: () => void;
2122
};
2223

23-
export const ProjectActionsMenu: FC<ProjectActionsMenuProps> = ({
24+
export const ProjectActionsMenu: FC<Props> = ({
2425
workspaceSlug,
2526
project,
2627
isAdmin,
@@ -29,10 +30,14 @@ export const ProjectActionsMenu: FC<ProjectActionsMenuProps> = ({
2930
onLeaveProject,
3031
onPublishModal,
3132
}) => {
33+
// states
34+
const [isMenuActive, setIsMenuActive] = useState(false);
35+
// translation
3236
const { t } = useTranslation();
33-
const navigate = useNavigate();
37+
// refs
3438
const actionSectionRef = useRef<HTMLDivElement | null>(null);
35-
const [isMenuActive, setIsMenuActive] = useState(false);
39+
// router
40+
const navigate = useNavigate();
3641

3742
return (
3843
<CustomMenu

apps/web/core/components/navigation/project-header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { FC } from "react";
2+
// plane imports
23
import { Logo } from "@plane/propel/emoji-icon-picker";
34
import type { TLogoProps } from "@plane/types";
4-
import { cn } from "@plane/utils";
55

66
type ProjectHeaderProps = {
77
project: {
@@ -11,7 +11,7 @@ type ProjectHeaderProps = {
1111
};
1212

1313
export const ProjectHeader: FC<ProjectHeaderProps> = ({ project }) => (
14-
<div className={cn("flex-grow flex items-center gap-1.5 text-left select-none w-full flex-shrink-0")}>
14+
<div className="flex items-center gap-1.5 text-left select-none w-full">
1515
<div className="size-7 rounded-md bg-custom-background-90 flex items-center justify-center flex-shrink-0">
1616
<Logo logo={project.logo_props} size={16} />
1717
</div>

apps/web/core/components/navigation/tab-navigation-overflow-menu.tsx

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import React from "react";
22
import { Link } from "react-router";
3-
import { MoreHorizontal, Star, Pin } from "lucide-react";
3+
import { MoreHorizontal, Pin } from "lucide-react";
4+
// plane imports
45
import { useTranslation } from "@plane/i18n";
6+
import { SetAsDefaultIcon } from "@plane/propel/icons";
57
import { Menu } from "@plane/propel/menu";
6-
import { TabNavigationItem } from "@plane/propel/tab-navigation";
8+
import { Tooltip } from "@plane/propel/tooltip";
79
import { cn } from "@plane/utils";
10+
// local imports
811
import type { TNavigationItem } from "./tab-navigation-root";
912
import type { TTabPreferences } from "./tab-navigation-utils";
1013

11-
export type TTabNavigationOverflowMenuProps = {
14+
type Props = {
1215
overflowItems: TNavigationItem[];
1316
isActive: (item: TNavigationItem) => boolean;
1417
tabPreferences: TTabPreferences;
@@ -19,9 +22,9 @@ export type TTabNavigationOverflowMenuProps = {
1922
/**
2023
* Overflow menu for tab navigation items
2124
* Displays items that don't fit in the visible area, with action icons
22-
* Shows "Eye" icon for user-hidden items, "Star" icon for all items
25+
* Shows "Eye" icon for user-hidden items, "Set as default" icon for all items
2326
*/
24-
export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps> = ({
27+
export const TabNavigationOverflowMenu: React.FC<Props> = ({
2528
overflowItems,
2629
isActive,
2730
tabPreferences,
@@ -48,23 +51,12 @@ export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps
4851
const isDefault = item.key === tabPreferences.defaultTab;
4952

5053
return (
51-
<Menu.MenuItem
52-
key={`${item.key}-overflow-${itemIsActive ? "active" : "inactive"}`}
53-
className={cn("p-0 w-full", {
54-
"bg-custom-background-80": itemIsActive,
55-
})}
56-
>
57-
<div className="flex items-center justify-between w-full group">
58-
<Link to={item.href} className="flex-1 min-w-0 w-full">
59-
<TabNavigationItem isActive={itemIsActive}>
60-
<span className="text-sm">{t(item.i18n_key)}</span>
61-
</TabNavigationItem>
54+
<Menu.MenuItem key={`${item.key}-overflow-${itemIsActive ? "active" : "inactive"}`} className="p-0 w-full">
55+
<div className="flex items-center justify-between w-full group/menu-item">
56+
<Link to={item.href} className="flex-1 min-w-0 w-full p-1">
57+
<span className="text-xs">{t(item.i18n_key)}</span>
6258
</Link>
63-
<div
64-
className={cn("flex items-center gap-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity", {
65-
"opacity-100": itemIsActive,
66-
})}
67-
>
59+
<div className="flex items-center">
6860
{/* Show Eye icon ONLY for user-hidden items */}
6961
{isHidden && (
7062
<button
@@ -74,23 +66,30 @@ export const TabNavigationOverflowMenu: React.FC<TTabNavigationOverflowMenuProps
7466
e.preventDefault();
7567
onShow(item.key);
7668
}}
77-
className="p-1 rounded hover:bg-custom-background-90"
69+
className="invisible group-hover/menu-item:visible p-1 rounded text-custom-text-300 hover:text-custom-text-100 transition-colors"
7870
title="Show"
7971
>
80-
<Pin className="h-3.5 w-3.5 text-custom-text-300 rotate-45" />
72+
<Pin className="size-3" />
8173
</button>
8274
)}
83-
<button
84-
onClick={(e) => {
85-
e.stopPropagation();
86-
e.preventDefault();
87-
onToggleDefault(item.key);
88-
}}
89-
className="p-1 rounded hover:bg-custom-background-90"
90-
title={isDefault ? "Clear default" : "Set as default"}
91-
>
92-
<Star className={`h-3.5 w-3.5 text-custom-text-300 ${isDefault ? "fill-current" : ""}`} />
93-
</button>
75+
<Tooltip tooltipContent={isDefault ? "Clear default" : "Set as default"}>
76+
<button
77+
onClick={(e) => {
78+
e.stopPropagation();
79+
e.preventDefault();
80+
onToggleDefault(item.key);
81+
}}
82+
className={cn(
83+
"invisible group-hover/menu-item:visible p-1 rounded text-custom-text-300 hover:text-custom-text-100 transition-colors",
84+
{
85+
visible: isDefault,
86+
}
87+
)}
88+
title={isDefault ? "Clear default" : "Set as default"}
89+
>
90+
<SetAsDefaultIcon className="size-3" />
91+
</button>
92+
</Tooltip>
9493
</div>
9594
</div>
9695
</Menu.MenuItem>

apps/web/core/components/navigation/tab-navigation-root.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,21 +167,23 @@ export const TabNavigationRoot: FC<TTabNavigationRootProps> = observer((props) =
167167
/>
168168

169169
{/* container for the tab navigation */}
170-
<div className="flex items-center gap-3 overflow-hidden pl-1.5 w-full h-full">
171-
<div className="flex items-center gap-2 flex-shrink-0 max-w-48 truncate">
170+
<div className="flex items-center gap-3 overflow-hidden pl-1.5 size-full">
171+
<div className="flex items-center gap-2 shrink-0">
172172
<ProjectHeader project={project} />
173-
<ProjectActionsMenu
174-
workspaceSlug={workspaceSlug}
175-
project={project}
176-
isAdmin={isAdmin}
177-
isAuthorized={isAuthorized}
178-
onCopyText={handleCopyText}
179-
onLeaveProject={handleLeaveProject}
180-
onPublishModal={() => handlePublishModal(true)}
181-
/>
173+
<div className="shrink-0">
174+
<ProjectActionsMenu
175+
workspaceSlug={workspaceSlug}
176+
project={project}
177+
isAdmin={isAdmin}
178+
isAuthorized={isAuthorized}
179+
onCopyText={handleCopyText}
180+
onLeaveProject={handleLeaveProject}
181+
onPublishModal={() => handlePublishModal(true)}
182+
/>
183+
</div>
182184
</div>
183185

184-
<div className="flex-shrink-0 h-5 w-1 border-l border-custom-border-200" />
186+
<div className="shrink-0 h-5 w-1 border-l border-custom-border-200" />
185187

186188
<div ref={containerRef} className="flex items-center h-full flex-1 min-w-0 overflow-hidden">
187189
<TabNavigationList className="h-full">

0 commit comments

Comments
 (0)