[WEB-5092] feat: app sidebar enhancements (#7946)

* chore: project sidebar and settings sidebar improvement

* chore: navigationitem ui improvement

* chore: workspace level new icon added

* chore: propel icon migration

* chore: code refactor

* chore: dashboard icon updated

* chore: code refactor

* chore: icons updated

* chore: code refactor

* chore: code refactor

* chore: scroll area component refactor

* chore: sidebar enhancements

* chore: add and archive icon updated

* chore: code refactor

* chore: code refactor

* chore: code refactor
This commit is contained in:
Anmol Singh Bhatia 2025-10-14 17:20:20 +05:30 committed by GitHub
parent 9cfde896b3
commit a3019ebd46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 93 additions and 43 deletions

View file

@ -1,8 +1,8 @@
import { observer } from "mobx-react";
import { Search } from "lucide-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// hooks
import { SidebarSearchButton } from "@/components/sidebar/search-button";
import { useCommandPalette } from "@/hooks/store/use-command-palette";
export const AppSearch = observer(() => {
@ -14,11 +14,10 @@ export const AppSearch = observer(() => {
return (
<button
type="button"
className="flex-shrink-0 size-8 aspect-square grid place-items-center rounded hover:bg-custom-sidebar-background-90 outline-none border-[0.5px] border-custom-sidebar-border-300"
onClick={() => toggleCommandPaletteModal(true)}
aria-label={t("aria_labels.projects_sidebar.open_command_palette")}
>
<Search className="size-4 text-custom-sidebar-text-300" />
<SidebarSearchButton isActive={false} />
</button>
);
});

View file

@ -60,11 +60,11 @@ export const NavItemChildren = observer((props: { projectId: string }) => {
className={cn(
"cursor-pointer relative group w-full flex items-center justify-between gap-1.5 rounded p-1 px-1.5 outline-none",
{
"text-custom-primary-200 bg-custom-primary-100/10": isActive,
"text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
"text-custom-text-200 bg-custom-background-80/75": isActive,
"text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
!isActive,
},
"text-sm font-medium"
"text-xs font-medium"
)}
>
{t(getProjectSettingsPageLabelI18nKey(link.key, link.i18n_label))}

View file

@ -14,7 +14,7 @@ export const SettingsSidebarHeader = observer((props: { customHeader?: React.Rea
return customHeader
? customHeader
: currentWorkspace && (
<div className="flex w-full gap-3 items-center justify-between pr-2">
<div className="flex w-full gap-3 items-center justify-between px-2">
<div className="flex w-full gap-3 items-center overflow-hidden">
<WorkspaceLogo
logo={currentWorkspace.logo_url ?? ""}

View file

@ -41,8 +41,8 @@ const SettingsSidebarNavItem = observer((props: TSettingsSidebarNavItemProps) =>
"flex w-full items-center px-2 py-1.5 rounded text-custom-text-200 justify-between",
"hover:bg-custom-primary-100/10",
{
"text-custom-primary-200 bg-custom-primary-100/10": typeof isActive === "function" ? isActive(setting) : isActive,
"hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
"text-custom-text-200 bg-custom-background-80/75": typeof isActive === "function" ? isActive(setting) : isActive,
"text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
typeof isActive === "function" ? !isActive(setting) : !isActive,
}
);
@ -83,14 +83,9 @@ const SettingsSidebarNavItem = observer((props: TSettingsSidebarNavItemProps) =>
</Disclosure.Button>
{/* Nested Navigation */}
{isExpanded && (
<Disclosure.Panel
as="div"
className={cn("flex flex-col gap-0.5", {
"space-y-0 ml-0": isExpanded,
})}
static
>
<div className="ml-4 border-l border-custom-border-200 pl-2 my-0.5">{renderChildren?.(setting.key)}</div>
<Disclosure.Panel as="div" className={cn("relative flex flex-col gap-0.5 mt-1 pl-6 mb-1.5")} static>
<div className="absolute left-[15px] top-0 bottom-1 w-[1px] bg-custom-border-200" />
{renderChildren?.(setting.key)}
</Disclosure.Panel>
)}
</Disclosure>

View file

@ -0,0 +1,25 @@
import React, { FC } from "react";
import { cn } from "@plane/utils";
type Props = React.ComponentProps<"button"> & {
label: React.ReactNode;
onClick: () => void;
};
export const SidebarAddButton: FC<Props> = (props) => {
const { label, onClick, disabled, ...rest } = props;
return (
<button
type="button"
className={cn(
"flex-grow text-custom-text-300 text-sm font-medium border-[0.5px] border-custom-sidebar-border-300 text-left rounded-md shadow-sm h-8 px-2 flex items-center gap-1.5",
!disabled && "hover:bg-custom-sidebar-background-90"
)}
onClick={onClick}
disabled={disabled}
{...rest}
>
{label}
</button>
);
};

View file

@ -0,0 +1,27 @@
import React, { FC } from "react";
import { Search } from "lucide-react";
import { cn } from "@plane/utils";
type Props = {
isActive?: boolean;
};
export const SidebarSearchButton: FC<Props> = (props) => {
const { isActive } = props;
return (
<div
className={cn(
"flex-shrink-0 size-8 aspect-square grid place-items-center rounded-md shadow-sm hover:bg-custom-sidebar-background-90 outline-none border-[0.5px] border-custom-sidebar-border-300",
{
"bg-custom-primary-100/10 hover:bg-custom-primary-100/10 border-custom-primary-200": isActive,
}
)}
>
<Search
className={cn("size-4 text-custom-sidebar-text-300", {
"text-custom-primary-200": isActive,
})}
/>
</div>
);
};

View file

@ -17,8 +17,8 @@ export const SidebarNavItem: FC<TSidebarNavItem> = (props) => {
className={cn(
"cursor-pointer relative group w-full flex items-center justify-between gap-1.5 rounded px-2 py-1 outline-none",
{
"text-custom-primary-200 bg-custom-primary-100/10": isActive,
"text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
"text-custom-text-200 bg-custom-background-80/75": isActive,
"text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
!isActive,
},
className

View file

@ -3,6 +3,7 @@ import { useEffect, useRef } from "react";
import { observer } from "mobx-react";
// plane helpers
import { useOutsideClickDetector } from "@plane/hooks";
import { ScrollArea } from "@plane/propel/scrollarea";
// components
import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-button";
import { SidebarDropdown } from "@/components/workspace/sidebar/dropdown";
@ -57,9 +58,16 @@ export const SidebarWrapper: FC<TSidebarWrapperProps> = observer((props) => {
{/* Quick actions */}
{quickActions}
</div>
<div className="flex flex-col gap-3 overflow-x-hidden scrollbar-sm h-full w-full overflow-y-auto vertical-scrollbar px-3 pt-3 pb-0.5">
<ScrollArea
orientation="vertical"
scrollType="hover"
size="sm"
rootClassName="size-full overflow-x-hidden overflow-y-auto"
viewportClassName="flex flex-col gap-3 overflow-x-hidden h-full w-full overflow-y-auto px-3 pt-3 pb-0.5"
>
{children}
</div>
</ScrollArea>
{/* Help Section */}
<div className="flex items-center justify-between p-3 border-t border-custom-border-200 bg-custom-sidebar-background-100 h-12">
<WorkspaceEditionBadge />

View file

@ -17,14 +17,14 @@ export const WorkspaceLogo = observer((props: Props) => {
<div
className={cn(
`relative grid h-6 w-6 flex-shrink-0 place-items-center uppercase ${
!props.logo && "rounded bg-[#026292] text-white"
!props.logo && "rounded-md bg-[#026292] text-white"
} ${props.classNames ? props.classNames : ""}`
)}
>
{props.logo && props.logo !== "" ? (
<img
src={getFileURL(props.logo)}
className="absolute left-0 top-0 h-full w-full rounded object-cover"
className="absolute left-0 top-0 h-full w-full rounded-md object-cover"
alt={t("aria_labels.projects_sidebar.workspace_logo")}
/>
) : (

View file

@ -176,7 +176,7 @@ export const ProjectNavigation: FC<TProjectItemsProps> = observer((props) => {
return (
<Link key={item.key} href={item.href} onClick={handleProjectClick}>
<SidebarNavItem className="pl-[18px]" isActive={!!isActive(item)}>
<SidebarNavItem isActive={!!isActive(item)}>
<div className="flex items-center gap-1.5 py-[1px]">
<item.icon className={`flex-shrink-0 size-4 ${item.name === "Intake" ? "stroke-1" : "stroke-[1.5]"}`} />
<span className="text-xs font-medium">{t(item.i18n_key)}</span>

View file

@ -402,7 +402,8 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
leaveTo="transform scale-95 opacity-0"
>
{isProjectListOpen && (
<Disclosure.Panel as="div" className="flex flex-col gap-0.5 mt-1">
<Disclosure.Panel as="div" className="relative flex flex-col gap-0.5 mt-1 pl-6 mb-1.5">
<div className="absolute left-[15px] top-0 bottom-1 w-[1px] bg-custom-border-200" />
<ProjectNavigationRoot workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
</Disclosure.Panel>
)}

View file

@ -9,6 +9,7 @@ import type { TIssue } from "@plane/types";
import { cn } from "@plane/utils";
// components
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal";
import { SidebarAddButton } from "@/components/sidebar/add-button";
// hooks
import { useCommandPalette } from "@/hooks/store/use-command-palette";
import { useProject } from "@/hooks/store/use-project";
@ -73,26 +74,20 @@ export const SidebarQuickActions = observer(() => {
fetchIssueDetails={false}
isDraft
/>
<div className={cn("flex items-center justify-between gap-1 cursor-pointer", {})}>
<button
type="button"
className={cn(
"relative flex flex-shrink-0 flex-grow items-center gap-2 h-8 text-custom-sidebar-text-300 rounded outline-none hover:bg-custom-sidebar-background-90 px-3 border-[0.5px] border-custom-sidebar-border-300",
{
"cursor-not-allowed opacity-50 ": disabled,
}
)}
data-ph-element={SIDEBAR_TRACKER_ELEMENTS.CREATE_WORK_ITEM_BUTTON}
onClick={() => {
toggleCreateIssueModal(true);
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
disabled={disabled}
>
<div className="flex items-center justify-between gap-2 cursor-pointer">
<SidebarAddButton
label={
<>
<AddIcon className="size-4" />
<span className="text-sm font-medium truncate max-w-[145px]">{t("sidebar.new_work_item")}</span>
</button>
</>
}
onClick={() => toggleCreateIssueModal(true)}
disabled={disabled}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
data-ph-element={SIDEBAR_TRACKER_ELEMENTS.CREATE_WORK_ITEM_BUTTON}
/>
<AppSearch />
</div>
</>