[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:
parent
9cfde896b3
commit
a3019ebd46
12 changed files with 93 additions and 43 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Search } from "lucide-react";
|
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
// hooks
|
// hooks
|
||||||
|
import { SidebarSearchButton } from "@/components/sidebar/search-button";
|
||||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||||
|
|
||||||
export const AppSearch = observer(() => {
|
export const AppSearch = observer(() => {
|
||||||
|
|
@ -14,11 +14,10 @@ export const AppSearch = observer(() => {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="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)}
|
onClick={() => toggleCommandPaletteModal(true)}
|
||||||
aria-label={t("aria_labels.projects_sidebar.open_command_palette")}
|
aria-label={t("aria_labels.projects_sidebar.open_command_palette")}
|
||||||
>
|
>
|
||||||
<Search className="size-4 text-custom-sidebar-text-300" />
|
<SidebarSearchButton isActive={false} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,11 @@ export const NavItemChildren = observer((props: { projectId: string }) => {
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-pointer relative group w-full flex items-center justify-between gap-1.5 rounded p-1 px-1.5 outline-none",
|
"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-text-200 bg-custom-background-80/75": isActive,
|
||||||
"text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
|
"text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
|
||||||
!isActive,
|
!isActive,
|
||||||
},
|
},
|
||||||
"text-sm font-medium"
|
"text-xs font-medium"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{t(getProjectSettingsPageLabelI18nKey(link.key, link.i18n_label))}
|
{t(getProjectSettingsPageLabelI18nKey(link.key, link.i18n_label))}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const SettingsSidebarHeader = observer((props: { customHeader?: React.Rea
|
||||||
return customHeader
|
return customHeader
|
||||||
? customHeader
|
? customHeader
|
||||||
: currentWorkspace && (
|
: 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">
|
<div className="flex w-full gap-3 items-center overflow-hidden">
|
||||||
<WorkspaceLogo
|
<WorkspaceLogo
|
||||||
logo={currentWorkspace.logo_url ?? ""}
|
logo={currentWorkspace.logo_url ?? ""}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
"flex w-full items-center px-2 py-1.5 rounded text-custom-text-200 justify-between",
|
||||||
"hover:bg-custom-primary-100/10",
|
"hover:bg-custom-primary-100/10",
|
||||||
{
|
{
|
||||||
"text-custom-primary-200 bg-custom-primary-100/10": typeof isActive === "function" ? isActive(setting) : isActive,
|
"text-custom-text-200 bg-custom-background-80/75": typeof isActive === "function" ? isActive(setting) : isActive,
|
||||||
"hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
|
"text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
|
||||||
typeof isActive === "function" ? !isActive(setting) : !isActive,
|
typeof isActive === "function" ? !isActive(setting) : !isActive,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -83,14 +83,9 @@ const SettingsSidebarNavItem = observer((props: TSettingsSidebarNavItemProps) =>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
{/* Nested Navigation */}
|
{/* Nested Navigation */}
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<Disclosure.Panel
|
<Disclosure.Panel as="div" className={cn("relative flex flex-col gap-0.5 mt-1 pl-6 mb-1.5")} static>
|
||||||
as="div"
|
<div className="absolute left-[15px] top-0 bottom-1 w-[1px] bg-custom-border-200" />
|
||||||
className={cn("flex flex-col gap-0.5", {
|
{renderChildren?.(setting.key)}
|
||||||
"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>
|
</Disclosure.Panel>
|
||||||
)}
|
)}
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
|
|
||||||
25
apps/web/core/components/sidebar/add-button.tsx
Normal file
25
apps/web/core/components/sidebar/add-button.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
27
apps/web/core/components/sidebar/search-button.tsx
Normal file
27
apps/web/core/components/sidebar/search-button.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -17,8 +17,8 @@ export const SidebarNavItem: FC<TSidebarNavItem> = (props) => {
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-pointer relative group w-full flex items-center justify-between gap-1.5 rounded px-2 py-1 outline-none",
|
"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-text-200 bg-custom-background-80/75": isActive,
|
||||||
"text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
|
"text-custom-sidebar-text-300 hover:bg-custom-sidebar-background-90 active:bg-custom-sidebar-background-90":
|
||||||
!isActive,
|
!isActive,
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useEffect, useRef } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// plane helpers
|
// plane helpers
|
||||||
import { useOutsideClickDetector } from "@plane/hooks";
|
import { useOutsideClickDetector } from "@plane/hooks";
|
||||||
|
import { ScrollArea } from "@plane/propel/scrollarea";
|
||||||
// components
|
// components
|
||||||
import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-button";
|
import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-button";
|
||||||
import { SidebarDropdown } from "@/components/workspace/sidebar/dropdown";
|
import { SidebarDropdown } from "@/components/workspace/sidebar/dropdown";
|
||||||
|
|
@ -57,9 +58,16 @@ export const SidebarWrapper: FC<TSidebarWrapperProps> = observer((props) => {
|
||||||
{/* Quick actions */}
|
{/* Quick actions */}
|
||||||
{quickActions}
|
{quickActions}
|
||||||
</div>
|
</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}
|
{children}
|
||||||
</div>
|
</ScrollArea>
|
||||||
{/* Help Section */}
|
{/* Help Section */}
|
||||||
<div className="flex items-center justify-between p-3 border-t border-custom-border-200 bg-custom-sidebar-background-100 h-12">
|
<div className="flex items-center justify-between p-3 border-t border-custom-border-200 bg-custom-sidebar-background-100 h-12">
|
||||||
<WorkspaceEditionBadge />
|
<WorkspaceEditionBadge />
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,14 @@ export const WorkspaceLogo = observer((props: Props) => {
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
`relative grid h-6 w-6 flex-shrink-0 place-items-center uppercase ${
|
`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.classNames ? props.classNames : ""}`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.logo && props.logo !== "" ? (
|
{props.logo && props.logo !== "" ? (
|
||||||
<img
|
<img
|
||||||
src={getFileURL(props.logo)}
|
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")}
|
alt={t("aria_labels.projects_sidebar.workspace_logo")}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ export const ProjectNavigation: FC<TProjectItemsProps> = observer((props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link key={item.key} href={item.href} onClick={handleProjectClick}>
|
<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]">
|
<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]"}`} />
|
<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>
|
<span className="text-xs font-medium">{t(item.i18n_key)}</span>
|
||||||
|
|
|
||||||
|
|
@ -402,7 +402,8 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
||||||
leaveTo="transform scale-95 opacity-0"
|
leaveTo="transform scale-95 opacity-0"
|
||||||
>
|
>
|
||||||
{isProjectListOpen && (
|
{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()} />
|
<ProjectNavigationRoot workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import type { TIssue } from "@plane/types";
|
||||||
import { cn } from "@plane/utils";
|
import { cn } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal";
|
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal";
|
||||||
|
import { SidebarAddButton } from "@/components/sidebar/add-button";
|
||||||
// hooks
|
// hooks
|
||||||
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
import { useCommandPalette } from "@/hooks/store/use-command-palette";
|
||||||
import { useProject } from "@/hooks/store/use-project";
|
import { useProject } from "@/hooks/store/use-project";
|
||||||
|
|
@ -73,26 +74,20 @@ export const SidebarQuickActions = observer(() => {
|
||||||
fetchIssueDetails={false}
|
fetchIssueDetails={false}
|
||||||
isDraft
|
isDraft
|
||||||
/>
|
/>
|
||||||
<div className={cn("flex items-center justify-between gap-1 cursor-pointer", {})}>
|
<div className="flex items-center justify-between gap-2 cursor-pointer">
|
||||||
<button
|
<SidebarAddButton
|
||||||
type="button"
|
label={
|
||||||
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}
|
|
||||||
>
|
|
||||||
<AddIcon className="size-4" />
|
<AddIcon className="size-4" />
|
||||||
<span className="text-sm font-medium truncate max-w-[145px]">{t("sidebar.new_work_item")}</span>
|
<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 />
|
<AppSearch />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue