Skip to content

Commit 993c789

Browse files
[WEB-3759] chore: header revamp for cycles, modules, pages and views (#6875)
* chore: header revamp for cycles, modules, pages and views * chore: moved list fetch to layout level
1 parent 2b411de commit 993c789

File tree

17 files changed

+269
-453
lines changed

17 files changed

+269
-453
lines changed

packages/types/src/common.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ export type TLogoProps = {
2626
export type TNameDescriptionLoader = "submitting" | "submitted" | "saved";
2727

2828
export type TFetchStatus = "partial" | "complete" | undefined;
29+
30+
export type ICustomSearchSelectOption = {
31+
value: any;
32+
query: string;
33+
content: React.ReactNode;
34+
disabled?: boolean;
35+
tooltip?: string | React.ReactNode;
36+
};

packages/ui/src/dropdowns/helper.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// FIXME: fix this!!!
22
import { Placement } from "@blueprintjs/popover2";
3+
import { ICustomSearchSelectOption } from "@plane/types";
34

45
export interface IDropdownProps {
56
customButtonClassName?: string;
@@ -44,15 +45,7 @@ interface CustomSearchSelectProps {
4445
onChange: any;
4546
onClose?: () => void;
4647
noResultsMessage?: string;
47-
options:
48-
| {
49-
value: any;
50-
query: string;
51-
content: React.ReactNode;
52-
disabled?: boolean;
53-
tooltip?: string | React.ReactNode;
54-
}[]
55-
| undefined;
48+
options?: ICustomSearchSelectOption[];
5649
}
5750

5851
interface SingleValueProps {

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/header.tsx

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useCallback, useState } from "react";
3+
import { useCallback, useRef, useState } from "react";
44
import { observer } from "mobx-react";
55
import Link from "next/link";
66
import { useParams } from "next/navigation";
@@ -18,12 +18,18 @@ import {
1818
// i18n
1919
import { useTranslation } from "@plane/i18n";
2020
// types
21-
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
21+
import {
22+
ICustomSearchSelectOption,
23+
IIssueDisplayFilterOptions,
24+
IIssueDisplayProperties,
25+
IIssueFilterOptions,
26+
} from "@plane/types";
2227
// ui
23-
import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip, Header } from "@plane/ui";
28+
import { Breadcrumbs, Button, ContrastIcon, CustomMenu, Tooltip, Header, CustomSearchSelect } from "@plane/ui";
2429
// components
2530
import { ProjectAnalyticsModal } from "@/components/analytics";
26-
import { BreadcrumbLink } from "@/components/common";
31+
import { BreadcrumbLink, SwitcherLabel } from "@/components/common";
32+
import { CycleQuickActions } from "@/components/cycles";
2733
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
2834
// helpers
2935
import { cn } from "@/helpers/common.helper";
@@ -69,6 +75,8 @@ const CycleDropdownOption: React.FC<{ cycleId: string }> = ({ cycleId }) => {
6975
};
7076

7177
export const CycleIssuesHeader: React.FC = observer(() => {
78+
// refs
79+
const parentRef = useRef<HTMLDivElement>(null);
7280
// states
7381
const [analyticsModal, setAnalyticsModal] = useState(false);
7482
// router
@@ -159,6 +167,25 @@ export const CycleIssuesHeader: React.FC = observer(() => {
159167
EUserPermissionsLevel.PROJECT
160168
);
161169

170+
const switcherOptions = currentProjectCycleIds
171+
?.map((id) => {
172+
const _cycle = id === cycleId ? cycleDetails : getCycleById(id);
173+
if (!_cycle) return;
174+
const cycleLink = `/${workspaceSlug}/projects/${projectId}/cycles/${_cycle.id}`;
175+
return {
176+
value: _cycle.id,
177+
query: _cycle.name,
178+
content: (
179+
<Link href={cycleLink}>
180+
<SwitcherLabel name={_cycle.name} LabelIcon={ContrastIcon} />
181+
</Link>
182+
),
183+
};
184+
})
185+
.filter((option) => option !== undefined) as ICustomSearchSelectOption[];
186+
187+
const workItemsCount = getGroupIssueCount(undefined, undefined, false);
188+
162189
const issuesCount = getGroupIssueCount(undefined, undefined, false);
163190

164191
return (
@@ -201,33 +228,29 @@ export const CycleIssuesHeader: React.FC = observer(() => {
201228
<Breadcrumbs.BreadcrumbItem
202229
type="component"
203230
component={
204-
<CustomMenu
231+
<CustomSearchSelect
232+
options={switcherOptions}
233+
value={cycleId}
234+
onChange={() => {}}
205235
label={
206-
<>
207-
<ContrastIcon className="h-3 w-3" />
208-
<div className="flex w-auto max-w-[70px] items-center gap-2 truncate sm:max-w-[200px]">
209-
<p className="truncate">{cycleDetails?.name && cycleDetails.name}</p>
210-
{issuesCount && issuesCount > 0 ? (
211-
<Tooltip
212-
isMobile={isMobile}
213-
tooltipContent={`There are ${issuesCount} ${
214-
issuesCount > 1 ? "work items" : "work item"
215-
} in this cycle`}
216-
position="bottom"
217-
>
218-
<span className="flex flex-shrink-0 cursor-default items-center justify-center rounded-xl bg-custom-primary-100/20 px-2 text-center text-xs font-semibold text-custom-primary-100">
219-
{issuesCount}
220-
</span>
221-
</Tooltip>
222-
) : null}
223-
</div>
224-
</>
236+
<div className="flex items-center gap-1">
237+
<SwitcherLabel name={cycleDetails?.name} LabelIcon={ContrastIcon} />
238+
{workItemsCount && workItemsCount > 0 ? (
239+
<Tooltip
240+
isMobile={isMobile}
241+
tooltipContent={`There are ${workItemsCount} ${
242+
workItemsCount > 1 ? "work items" : "work item"
243+
} in this cycle`}
244+
position="bottom"
245+
>
246+
<span className="flex flex-shrink-0 cursor-default items-center justify-center rounded-xl bg-custom-primary-100/20 px-2 text-center text-xs font-semibold text-custom-primary-100">
247+
{workItemsCount}
248+
</span>
249+
</Tooltip>
250+
) : null}
251+
</div>
225252
}
226-
className="ml-1.5 flex-shrink-0 truncate"
227-
placement="bottom-start"
228-
>
229-
{currentProjectCycleIds?.map((cycleId) => <CycleDropdownOption key={cycleId} cycleId={cycleId} />)}
230-
</CustomMenu>
253+
/>
231254
}
232255
/>
233256
</Breadcrumbs>
@@ -302,19 +325,19 @@ export const CycleIssuesHeader: React.FC = observer(() => {
302325
)}
303326
<button
304327
type="button"
305-
className="grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80"
328+
className="p-1 rounded outline-none hover:bg-custom-sidebar-background-80 bg-custom-background-80/70"
306329
onClick={toggleSidebar}
307330
>
308-
<ArrowRight className={`h-4 w-4 duration-300 ${isSidebarCollapsed ? "-rotate-180" : ""}`} />
331+
<PanelRight className={cn("h-4 w-4", !isSidebarCollapsed ? "text-[#3E63DD]" : "text-custom-text-200")} />
309332
</button>
333+
<CycleQuickActions
334+
parentRef={parentRef}
335+
cycleId={cycleId}
336+
projectId={projectId}
337+
workspaceSlug={workspaceSlug}
338+
customClassName="flex-shrink-0 flex items-center justify-center size-6 bg-custom-background-80/70 rounded"
339+
/>
310340
</div>
311-
<button
312-
type="button"
313-
className="grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 md:hidden"
314-
onClick={toggleSidebar}
315-
>
316-
<PanelRight className={cn("h-4 w-4", !isSidebarCollapsed ? "text-[#3E63DD]" : "text-custom-text-200")} />
317-
</button>
318341
</Header.RightItem>
319342
</Header>
320343
</>

web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/header.tsx

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@ import {
1616
EUserPermissionsLevel,
1717
} from "@plane/constants";
1818
// types
19-
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
19+
import {
20+
ICustomSearchSelectOption,
21+
IIssueDisplayFilterOptions,
22+
IIssueDisplayProperties,
23+
IIssueFilterOptions,
24+
} from "@plane/types";
2025
// ui
21-
import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip, Header } from "@plane/ui";
26+
import { Breadcrumbs, Button, CustomMenu, DiceIcon, Tooltip, Header, CustomSearchSelect } from "@plane/ui";
2227
// components
2328
import { ProjectAnalyticsModal } from "@/components/analytics";
24-
import { BreadcrumbLink } from "@/components/common";
29+
import { BreadcrumbLink, SwitcherLabel } from "@/components/common";
2530
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
2631
// helpers
2732
import { cn } from "@/helpers/common.helper";
@@ -155,7 +160,24 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
155160
EUserPermissionsLevel.PROJECT
156161
);
157162

158-
const issuesCount = getGroupIssueCount(undefined, undefined, false);
163+
const workItemsCount = getGroupIssueCount(undefined, undefined, false);
164+
165+
const switcherOptions = projectModuleIds
166+
?.map((id) => {
167+
const _module = id === moduleId ? moduleDetails : getModuleById(id);
168+
if (!_module) return;
169+
const moduleLink = `/${workspaceSlug}/projects/${projectId}/modules/${_module.id}`;
170+
return {
171+
value: _module.id,
172+
query: _module.name,
173+
content: (
174+
<Link href={moduleLink}>
175+
<SwitcherLabel name={_module.name} LabelIcon={DiceIcon} />
176+
</Link>
177+
),
178+
};
179+
})
180+
.filter((option) => option !== undefined) as ICustomSearchSelectOption[];
159181

160182
return (
161183
<>
@@ -196,33 +218,29 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
196218
<Breadcrumbs.BreadcrumbItem
197219
type="component"
198220
component={
199-
<CustomMenu
221+
<CustomSearchSelect
222+
options={switcherOptions}
200223
label={
201-
<>
202-
<DiceIcon className="h-3 w-3" />
203-
<div className="flex w-auto max-w-[70px] items-center gap-2 truncate sm:max-w-[200px]">
204-
<p className="truncate">{moduleDetails?.name && moduleDetails.name}</p>
205-
{issuesCount && issuesCount > 0 ? (
206-
<Tooltip
207-
isMobile={isMobile}
208-
tooltipContent={`There are ${issuesCount} ${
209-
issuesCount > 1 ? "work items" : "work item"
210-
} in this module`}
211-
position="bottom"
212-
>
213-
<span className="flex flex-shrink-0 cursor-default items-center justify-center rounded-xl bg-custom-primary-100/20 px-2 text-center text-xs font-semibold text-custom-primary-100">
214-
{issuesCount}
215-
</span>
216-
</Tooltip>
217-
) : null}
218-
</div>
219-
</>
224+
<div className="flex items-center gap-1">
225+
<SwitcherLabel name={moduleDetails?.name} LabelIcon={DiceIcon} />
226+
{workItemsCount && workItemsCount > 0 ? (
227+
<Tooltip
228+
isMobile={isMobile}
229+
tooltipContent={`There are ${workItemsCount} ${
230+
workItemsCount > 1 ? "work items" : "work item"
231+
} in this module`}
232+
position="bottom"
233+
>
234+
<span className="flex flex-shrink-0 cursor-default items-center justify-center rounded-xl bg-custom-primary-100/20 px-2 text-center text-xs font-semibold text-custom-primary-100">
235+
{workItemsCount}
236+
</span>
237+
</Tooltip>
238+
) : null}
239+
</div>
220240
}
221-
className="ml-1.5 flex-shrink-0"
222-
placement="bottom-start"
223-
>
224-
{projectModuleIds?.map((moduleId) => <ModuleDropdownOption key={moduleId} moduleId={moduleId} />)}
225-
</CustomMenu>
241+
value={moduleId}
242+
onChange={() => {}}
243+
/>
226244
}
227245
/>
228246
</Breadcrumbs>

0 commit comments

Comments
 (0)