[WEB-5614] chore: platform header and breadcrumb enhancements (#8383)
This commit is contained in:
parent
3df58397b5
commit
b165e2a3fe
16 changed files with 163 additions and 85 deletions
|
|
@ -14,6 +14,7 @@ import {
|
|||
import { usePlatformOS } from "@plane/hooks";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { IconButton } from "@plane/propel/icon-button";
|
||||
import { CycleIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import type { ICustomSearchSelectOption, IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
|
||||
|
|
@ -236,7 +237,6 @@ export const CycleIssuesHeader = observer(function CycleIssuesHeader() {
|
|||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="self-start"
|
||||
onClick={() => {
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||
}}
|
||||
|
|
@ -247,9 +247,15 @@ export const CycleIssuesHeader = observer(function CycleIssuesHeader() {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
<Button variant="ghost" size="lg" onClick={toggleSidebar}>
|
||||
<PanelRight className={cn("h-4 w-4", !isSidebarCollapsed ? "text-accent-primary" : "text-secondary")} />
|
||||
</Button>
|
||||
<IconButton
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
icon={PanelRight}
|
||||
onClick={toggleSidebar}
|
||||
className={cn({
|
||||
"text-accent-primary bg-accent-subtle": !isSidebarCollapsed,
|
||||
})}
|
||||
/>
|
||||
<CycleQuickActions
|
||||
parentRef={parentRef}
|
||||
cycleId={cycleId}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import useLocalStorage from "@/hooks/use-local-storage";
|
|||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web imports
|
||||
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
|
||||
import { IconButton } from "@plane/propel/icon-button";
|
||||
|
||||
export const ModuleIssuesHeader = observer(function ModuleIssuesHeader() {
|
||||
// refs
|
||||
|
|
@ -242,9 +243,15 @@ export const ModuleIssuesHeader = observer(function ModuleIssuesHeader() {
|
|||
) : (
|
||||
<></>
|
||||
)}
|
||||
<Button variant="ghost" size="lg" onClick={toggleSidebar}>
|
||||
<PanelRight className={cn("h-4 w-4", !isSidebarCollapsed ? "text-accent-primary" : "text-secondary")} />
|
||||
</Button>
|
||||
<IconButton
|
||||
variant="tertiary"
|
||||
size="lg"
|
||||
icon={PanelRight}
|
||||
onClick={toggleSidebar}
|
||||
className={cn({
|
||||
"text-accent-primary bg-accent-subtle": !isSidebarCollapsed,
|
||||
})}
|
||||
/>
|
||||
{moduleId && (
|
||||
<ModuleQuickActions
|
||||
parentRef={parentRef}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { EmptyStateDetailed } from "@plane/propel/empty-state";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import type { ICycle } from "@plane/types";
|
||||
|
|
@ -15,7 +16,6 @@ import { ActiveCycleProgress } from "@/components/cycles/active-cycle/progress";
|
|||
import useCyclesDetails from "@/components/cycles/active-cycle/use-cycles-details";
|
||||
import { CycleListGroupHeader } from "@/components/cycles/list/cycle-list-group-header";
|
||||
import { CyclesListItem } from "@/components/cycles/list/cycles-list-item";
|
||||
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
|
||||
// hooks
|
||||
import { useCycle } from "@/hooks/store/use-cycle";
|
||||
import type { ActiveCycleIssueDetails } from "@/store/issue/cycle";
|
||||
|
|
@ -50,10 +50,11 @@ const ActiveCyclesComponent = observer(function ActiveCyclesComponent({
|
|||
|
||||
if (!cycleId || !activeCycle) {
|
||||
return (
|
||||
<DetailedEmptyState
|
||||
<EmptyStateDetailed
|
||||
assetKey="cycle"
|
||||
title={t("project_cycles.empty_state.active.title")}
|
||||
description={t("project_cycles.empty_state.active.description")}
|
||||
assetPath={activeCycleResolvedPath}
|
||||
rootClassName="py-10 h-auto"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -114,7 +115,7 @@ export const ActiveCycleRoot = observer(function ActiveCycleRoot(props: IActiveC
|
|||
<Disclosure as="div" className="flex flex-shrink-0 flex-col" defaultOpen>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-subtle bg-layer-2 cursor-pointer">
|
||||
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-subtle bg-layer-1 cursor-pointer">
|
||||
<CycleListGroupHeader title={t("project_cycles.active_cycle.label")} type="current" isExpanded={open} />
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel>
|
||||
|
|
|
|||
|
|
@ -70,49 +70,50 @@ export const CyclesViewHeader = observer(function CyclesViewHeader(props: Props)
|
|||
}, [searchQuery]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{!isSearchOpen && (
|
||||
<div className="flex items-center gap-2">
|
||||
{!isSearchOpen ? (
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="lg"
|
||||
className="-mr-5"
|
||||
onClick={() => {
|
||||
setIsSearchOpen(true);
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
icon={Search}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"ml-auto flex items-center justify-start gap-1 rounded-md border border-transparent bg-surface-1 text-placeholder w-0 transition-[width] ease-linear overflow-hidden opacity-0",
|
||||
{
|
||||
"w-64 px-2.5 py-1.5 border-subtle opacity-100": isSearchOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-13 text-primary placeholder:text-placeholder focus:outline-none"
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
onChange={(e) => updateSearchQuery(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
{isSearchOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center"
|
||||
onClick={() => {
|
||||
updateSearchQuery("");
|
||||
setIsSearchOpen(false);
|
||||
}}
|
||||
>
|
||||
<CloseIcon className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"ml-auto flex items-center justify-start gap-1 rounded-md border border-transparent bg-surface-1 text-placeholder w-0 transition-[width] ease-linear overflow-hidden opacity-0",
|
||||
{
|
||||
"w-64 px-2.5 py-1.5 border-subtle opacity-100": isSearchOpen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="w-full max-w-[234px] border-none bg-transparent text-13 text-primary placeholder:text-placeholder focus:outline-none"
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
onChange={(e) => updateSearchQuery(e.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
{isSearchOpen && (
|
||||
<button
|
||||
type="button"
|
||||
className="grid place-items-center"
|
||||
onClick={() => {
|
||||
updateSearchQuery("");
|
||||
setIsSearchOpen(false);
|
||||
}}
|
||||
>
|
||||
<CloseIcon className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FiltersDropdown
|
||||
icon={<ListFilter className="h-3 w-3" />}
|
||||
title={t("common.filters")}
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ export const CycleListItemAction = observer(function CycleListItemAction(props:
|
|||
{createdByDetails && !isActive && <ButtonAvatars showTooltip={false} userIds={createdByDetails?.id} />}
|
||||
{!isActive && (
|
||||
<Tooltip tooltipContent={`${cycleDetails.assignee_ids?.length} Members`} isMobile={isMobile}>
|
||||
<div className="flex w-10 cursor-default items-center justify-center">
|
||||
<div className="flex w-min cursor-default items-center justify-center">
|
||||
{cycleDetails.assignee_ids && cycleDetails.assignee_ids?.length > 0 ? (
|
||||
<AvatarGroup showTooltip={false}>
|
||||
{cycleDetails.assignee_ids?.map((assignee_id) => {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export const CyclesList = observer(function CyclesList(props: ICyclesList) {
|
|||
<Disclosure as="div" className="flex flex-shrink-0 flex-col" defaultOpen>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-subtle bg-layer-2 cursor-pointer">
|
||||
<Disclosure.Button className="sticky top-0 z-[2] w-full flex-shrink-0 border-b border-subtle bg-layer-1 cursor-pointer">
|
||||
<CycleListGroupHeader
|
||||
title={t("project_cycles.upcoming_cycle.label")}
|
||||
type="upcoming"
|
||||
|
|
@ -59,7 +59,7 @@ export const CyclesList = observer(function CyclesList(props: ICyclesList) {
|
|||
<Disclosure as="div" className="flex flex-shrink-0 flex-col pb-7">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button className="sticky top-0 z-2 w-full flex-shrink-0 border-b border-subtle bg-layer-2 cursor-pointer">
|
||||
<Disclosure.Button className="sticky top-0 z-2 w-full flex-shrink-0 border-b border-subtle bg-layer-1 cursor-pointer">
|
||||
<CycleListGroupHeader
|
||||
title={t("project_cycles.completed_cycle.label")}
|
||||
type="completed"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
|
||||
// ui
|
||||
import {
|
||||
|
|
@ -9,6 +10,7 @@ import {
|
|||
CYCLE_TRACKER_ELEMENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IconButton } from "@plane/propel/icon-button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
|
|
@ -155,7 +157,13 @@ export const CycleQuickActions = observer(function CycleQuickActions(props: Prop
|
|||
</div>
|
||||
)}
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect maxHeight="lg" buttonClassName={customClassName}>
|
||||
<CustomMenu
|
||||
customButton={<IconButton variant="tertiary" size="lg" icon={MoreHorizontal} />}
|
||||
placement="bottom-end"
|
||||
closeOnSelect
|
||||
maxHeight="lg"
|
||||
buttonClassName={customClassName}
|
||||
>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export const ModuleViewHeader = observer(function ModuleViewHeader() {
|
|||
const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0 || displayFilters?.favorites;
|
||||
|
||||
return (
|
||||
<div className="hidden h-full sm:flex items-center gap-3 self-end">
|
||||
<div className="hidden h-full sm:flex items-center gap-2 self-end">
|
||||
<div className="flex items-center">
|
||||
{!isSearchOpen && (
|
||||
<IconButton
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
|
||||
// plane imports
|
||||
import {
|
||||
|
|
@ -9,6 +10,7 @@ import {
|
|||
MODULE_TRACKER_EVENTS,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IconButton } from "@plane/propel/icon-button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ContextMenu, CustomMenu } from "@plane/ui";
|
||||
|
|
@ -112,15 +114,18 @@ export const ModuleQuickActions = observer(function ModuleQuickActions(props: Pr
|
|||
const MENU_ITEMS: TContextMenuItem[] = Array.isArray(menuResult) ? menuResult : menuResult.items;
|
||||
const additionalModals = Array.isArray(menuResult) ? null : menuResult.modals;
|
||||
|
||||
const CONTEXT_MENU_ITEMS: TContextMenuItem[] = MENU_ITEMS.map((item) => ({
|
||||
...item,
|
||||
action: () => {
|
||||
captureClick({
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
item.action();
|
||||
},
|
||||
}));
|
||||
const CONTEXT_MENU_ITEMS = MENU_ITEMS.map(function CONTEXT_MENU_ITEMS(item) {
|
||||
return {
|
||||
...item,
|
||||
|
||||
onClick: () => {
|
||||
captureClick({
|
||||
elementName: MODULE_TRACKER_ELEMENTS.CONTEXT_MENU,
|
||||
});
|
||||
item.action();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -145,7 +150,12 @@ export const ModuleQuickActions = observer(function ModuleQuickActions(props: Pr
|
|||
</div>
|
||||
)}
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect buttonClassName={customClassName}>
|
||||
<CustomMenu
|
||||
customButton={<IconButton variant="tertiary" size="lg" icon={MoreHorizontal} />}
|
||||
placement="bottom-end"
|
||||
closeOnSelect
|
||||
buttonClassName={customClassName}
|
||||
>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { observer } from "mobx-react";
|
||||
import { ListFilter } from "lucide-react";
|
||||
// plane imports
|
||||
import { IconButton } from "@plane/propel/icon-button";
|
||||
import { FilterIcon, FilterAppliedIcon } from "@plane/propel/icons";
|
||||
import { cn } from "@plane/utils";
|
||||
import type { IFilterInstance } from "@plane/shared-state";
|
||||
import type { TExternalFilter, TFilterProperty } from "@plane/types";
|
||||
import { cn } from "@plane/ui";
|
||||
// components
|
||||
import { AddFilterButton } from "@/components/rich-filters/add-filters/button";
|
||||
|
||||
|
|
@ -49,28 +50,14 @@ export const FiltersToggle = observer(function FiltersToggle<P extends TFilterPr
|
|||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(COMMON_CLASSNAME, {
|
||||
"border-transparent bg-accent-primary/10 hover:bg-accent-primary/20": isFilterRowVisible,
|
||||
"hover:bg-surface-1": !isFilterRowVisible,
|
||||
})}
|
||||
<IconButton
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
icon={showFilterRowChangesPill ? FilterAppliedIcon : FilterIcon}
|
||||
onClick={handleToggleFilter}
|
||||
>
|
||||
<div className="relative">
|
||||
<ListFilter
|
||||
className={cn("size-4", {
|
||||
"text-accent-primary": isFilterRowVisible,
|
||||
"text-tertiary": !isFilterRowVisible,
|
||||
})}
|
||||
/>
|
||||
{showFilterRowChangesPill && (
|
||||
<span
|
||||
className={cn("p-[3px] rounded-full bg-accent-primary absolute top-[0.2px] -right-[0.4px]", {
|
||||
"bg-layer-1": hasAnyConditions === false && filter?.hasChanges === true, // If there are no conditions and there are changes, show the pill in the background color
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
className={cn({
|
||||
"text-accent-primary bg-accent-subtle border border-accent-subtle-1": showFilterRowChangesPill,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
// types
|
||||
import { EUserPermissions, EUserPermissionsLevel, PROJECT_VIEW_TRACKER_ELEMENTS } from "@plane/constants";
|
||||
import { IconButton } from "@plane/propel/icon-button";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type { IProjectView } from "@plane/types";
|
||||
// ui
|
||||
|
|
@ -96,7 +98,12 @@ export const ViewQuickActions = observer(function ViewQuickActions(props: Props)
|
|||
<PublishViewModal isOpen={isPublishModalOpen} onClose={() => setPublishModalOpen(false)} view={view} />
|
||||
{additionalModals}
|
||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||
<CustomMenu ellipsis placement="bottom-end" closeOnSelect buttonClassName={customClassName}>
|
||||
<CustomMenu
|
||||
customButton={<IconButton variant="tertiary" size="lg" icon={MoreHorizontal} />}
|
||||
placement="bottom-end"
|
||||
closeOnSelect
|
||||
buttonClassName={customClassName}
|
||||
>
|
||||
{MENU_ITEMS.map((item) => {
|
||||
if (item.shouldRender === false) return null;
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue