diff --git a/packages/tailwind-config-custom/tailwind.config.js b/packages/tailwind-config-custom/tailwind.config.js index 4f032e0f8..a708a6221 100644 --- a/packages/tailwind-config-custom/tailwind.config.js +++ b/packages/tailwind-config-custom/tailwind.config.js @@ -333,6 +333,8 @@ module.exports = { 72: "16.2rem", 80: "18rem", 96: "21.6rem", + "page-x": "1.35rem", + "page-y": "1.35rem" }, margin: { 0: "0", @@ -434,5 +436,23 @@ module.exports = { custom: ["Inter", "sans-serif"], }, }, - plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")], + plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography"), function({ addUtilities }) { + const newUtilities = { + // Mobile screens + '.px-page-x': { + paddingLeft: '0.675rem', + paddingRight: '0.675rem', + }, + // Medium screens (768px and up) + '@media (min-width: 768px)': { + '.px-page-x': { + paddingLeft: '1.35rem', + paddingRight: '1.35rem', + }, + } + }; + + addUtilities(newUtilities, ['responsive']); + }, +], }; diff --git a/packages/ui/src/header/header.tsx b/packages/ui/src/header/header.tsx new file mode 100644 index 000000000..97b7ed2eb --- /dev/null +++ b/packages/ui/src/header/header.tsx @@ -0,0 +1,39 @@ +import * as React from "react"; +import { cn } from "../../helpers"; +import { EHeaderVariant, getHeaderStyle, THeaderVariant } from "./helper"; +import { ERowVariant, CustomRow } from "../row"; + +export interface CustomHeaderProps { + variant?: THeaderVariant; + setHeight?: boolean; + className?: string; + children: React.ReactNode; +} + +const CustomHeader = (props: CustomHeaderProps) => { + const { variant = EHeaderVariant.PRIMARY, className = "", setHeight = true, children, ...rest } = props; + + const style = getHeaderStyle(variant, setHeight); + return ( + + {children} + + ); +}; + +const LeftItem = (props: CustomHeaderProps) => ( +
{props.children}
+); +const RightItem = (props: CustomHeaderProps) => ( +
{props.children}
+); + +CustomHeader.LeftItem = LeftItem; +CustomHeader.RightItem = RightItem; +CustomHeader.displayName = "plane-ui-header"; + +export { CustomHeader, EHeaderVariant }; diff --git a/packages/ui/src/header/helper.tsx b/packages/ui/src/header/helper.tsx new file mode 100644 index 000000000..22adc646b --- /dev/null +++ b/packages/ui/src/header/helper.tsx @@ -0,0 +1,25 @@ +export enum EHeaderVariant { + PRIMARY = "primary", + SECONDARY = "secondary", + TERNARY = "ternary", +} +export type THeaderVariant = EHeaderVariant.PRIMARY | EHeaderVariant.SECONDARY | EHeaderVariant.TERNARY; + +export interface IHeaderProperties { + [key: string]: string; +} +export const headerStyle: IHeaderProperties = { + [EHeaderVariant.PRIMARY]: + "relative flex w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 bg-custom-sidebar-background-100", + [EHeaderVariant.SECONDARY]: "block !py-0 overflow-y-hidden border-b border-custom-border-200", + [EHeaderVariant.TERNARY]: "flex justify-between py-2 border-b border-custom-border-200", +}; +export const minHeights: IHeaderProperties = { + [EHeaderVariant.PRIMARY]: "", + [EHeaderVariant.SECONDARY]: "min-h-[52px]", + [EHeaderVariant.TERNARY]: "", +}; +export const getHeaderStyle = (variant: THeaderVariant, setMinHeight: boolean) => { + const height = setMinHeight ? minHeights[variant] : ""; + return headerStyle[variant] + " " + height; +}; diff --git a/packages/ui/src/header/index.ts b/packages/ui/src/header/index.ts new file mode 100644 index 000000000..49ac70fe2 --- /dev/null +++ b/packages/ui/src/header/index.ts @@ -0,0 +1 @@ +export * from "./header"; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 34eff7648..929f99a5f 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -23,3 +23,5 @@ export * from "./loader"; export * from "./collapsible"; export * from "./popovers"; export * from "./tables"; +export * from "./header"; +export * from "./row"; diff --git a/packages/ui/src/row/helper.tsx b/packages/ui/src/row/helper.tsx new file mode 100644 index 000000000..15982fddb --- /dev/null +++ b/packages/ui/src/row/helper.tsx @@ -0,0 +1,12 @@ +export enum ERowVariant { + REGULAR = "regular", + HUGGING = "hugging", +} +export type TRowVariant = ERowVariant.REGULAR | ERowVariant.HUGGING; +export interface IRowProperties { + [key: string]: string; +} +export const rowStyle: IRowProperties = { + [ERowVariant.REGULAR]: "px-page-x", + [ERowVariant.HUGGING]: "px-0", +}; diff --git a/packages/ui/src/row/index.ts b/packages/ui/src/row/index.ts new file mode 100644 index 000000000..5494453a7 --- /dev/null +++ b/packages/ui/src/row/index.ts @@ -0,0 +1 @@ +export * from "./row"; diff --git a/packages/ui/src/row/row.tsx b/packages/ui/src/row/row.tsx new file mode 100644 index 000000000..c16fd9354 --- /dev/null +++ b/packages/ui/src/row/row.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { cn } from "../../helpers"; +import { ERowVariant, rowStyle, TRowVariant } from "./helper"; + +export interface CustomRowProps extends React.HTMLAttributes { + variant?: TRowVariant; + className?: string; + children: React.ReactNode; +} + +const CustomRow = React.forwardRef((props, ref) => { + const { variant = ERowVariant.REGULAR, className = "", children, ...rest } = props; + + const style = rowStyle[variant]; + + return ( +
+ {children} +
+ ); +}); + +CustomRow.displayName = "plane-ui-row"; + +export { CustomRow, ERowVariant }; diff --git a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx index 26d87a5a6..5dfbf976f 100644 --- a/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/active-cycles/header.tsx @@ -2,29 +2,27 @@ import { observer } from "mobx-react"; // ui -import { Breadcrumbs, ContrastIcon } from "@plane/ui"; +import { Breadcrumbs, ContrastIcon, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; // plane web components import { UpgradeBadge } from "@/plane-web/components/workspace"; export const WorkspaceActiveCycleHeader = observer(() => ( -
-
-
- - } - /> - } - /> - - -
-
-
+ + + + } + /> + } + /> + + + + )); diff --git a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx index dc503dd6d..d7f3d3e9a 100644 --- a/web/app/[workspaceSlug]/(projects)/analytics/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/analytics/header.tsx @@ -6,7 +6,7 @@ import { useSearchParams } from "next/navigation"; // icons import { BarChart2, PanelRight } from "lucide-react"; // ui -import { Breadcrumbs } from "@plane/ui"; +import { Breadcrumbs, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; // helpers @@ -36,38 +36,32 @@ export const WorkspaceAnalyticsHeader = observer(() => { }, [toggleWorkspaceAnalyticsSidebar, workspaceAnalyticsSidebarCollapsed]); return ( - <> -
-
-
- - } /> - } - /> - - {analytics_tab === "custom" && ( - - )} -
-
-
- + + + + } />} + /> + + {analytics_tab === "custom" ? ( + + ) : ( + <> + )} + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx index 240993a24..d18249275 100644 --- a/web/app/[workspaceSlug]/(projects)/analytics/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/analytics/page.tsx @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; import { Tab } from "@headlessui/react"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { CustomAnalytics, ScopeAndDemand } from "@/components/analytics"; import { PageHead } from "@/components/core"; import { EmptyState } from "@/components/empty-state"; @@ -34,24 +35,26 @@ const AnalyticsPage = observer(() => { {workspaceProjectIds.length > 0 || loader ? (
- - {ANALYTICS_TABS.map((tab) => ( - - {({ selected }) => ( - - )} - - ))} - + + + {ANALYTICS_TABS.map((tab) => ( + + {({ selected }) => ( + + )} + + ))} + + diff --git a/web/app/[workspaceSlug]/(projects)/header.tsx b/web/app/[workspaceSlug]/(projects)/header.tsx index 3f6456328..bbd92bf0a 100644 --- a/web/app/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/header.tsx @@ -7,7 +7,7 @@ import { Home } from "lucide-react"; import githubBlackImage from "/public/logos/github-black.png"; import githubWhiteImage from "/public/logos/github-white.png"; // ui -import { Breadcrumbs } from "@plane/ui"; +import { Breadcrumbs, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; // constants @@ -22,8 +22,8 @@ export const WorkspaceDashboardHeader = () => { return ( <> - + + ); }; diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx index 845ddf312..0414d303c 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/header.tsx @@ -7,7 +7,7 @@ import Link from "next/link"; import { useParams } from "next/navigation"; import { ChevronDown, PanelRight } from "lucide-react"; import { IUserProfileProjectSegregation } from "@plane/types"; -import { Breadcrumbs, CustomMenu, UserActivityIcon } from "@plane/ui"; +import { Breadcrumbs, CustomHeader, CustomMenu, UserActivityIcon } from "@plane/ui"; import { BreadcrumbLink } from "@/components/common"; // components import { ProfileIssuesFilter } from "@/components/profile"; @@ -46,8 +46,8 @@ export const UserProfileHeader: FC = observer((props) => { const breadcrumbLabel = `${isCurrentUser ? "Your" : userName} Work`; return ( -
-
+ +
= observer((props) => {
-
-
+ + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx b/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx index 39fe4c6d5..d91c556c9 100644 --- a/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx +++ b/web/app/[workspaceSlug]/(projects)/profile/[userId]/navbar.tsx @@ -5,6 +5,7 @@ import { useParams, usePathname } from "next/navigation"; // components // constants +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "@/constants/profile"; type Props = { @@ -20,7 +21,7 @@ export const ProfileNavbar: React.FC = (props) => { const tabsList = isAuthorized ? [...PROFILE_VIEWER_TAB, ...PROFILE_ADMINS_TAB] : PROFILE_VIEWER_TAB; return ( -
+
{tabsList.map((tab) => ( @@ -36,6 +37,6 @@ export const ProfileNavbar: React.FC = (props) => { ))}
-
+ ); }; diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx index 0d68d7d61..ce7a07278 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/header.tsx @@ -4,7 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // ui -import { ArchiveIcon, Breadcrumbs, Tooltip } from "@plane/ui"; +import { ArchiveIcon, Breadcrumbs, Tooltip, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; // constants @@ -16,8 +16,8 @@ import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; type TProps = { - activeTab: 'issues' | 'cycles' | 'modules'; -} + activeTab: "issues" | "cycles" | "modules"; +}; export const ProjectArchivesHeader: FC = observer((props: TProps) => { const { activeTab } = props; @@ -38,8 +38,8 @@ export const ProjectArchivesHeader: FC = observer((props: TProps) => { PROJECT_ARCHIVES_BREADCRUMB_LIST[activeTab as keyof typeof PROJECT_ARCHIVES_BREADCRUMB_LIST]; return ( -
-
+ +
= observer((props: TProps) => { ) : null}
-
-
+ + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx index e09438587..d4bdf3535 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; // ui -import { ArchiveIcon, Breadcrumbs, LayersIcon } from "@plane/ui"; +import { ArchiveIcon, Breadcrumbs, LayersIcon, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { IssueDetailQuickActions } from "@/components/issues"; @@ -36,66 +36,66 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => { ); return ( -
-
-
- - - - - ) - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - - } - /> - -
-
- -
+ + + + + + + ) + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + + } + /> + + + + + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx index 69c464436..ab1a368ab 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx @@ -70,7 +70,7 @@ const CycleDetailPage = observer(() => { {cycleId && !isSidebarCollapsed && (
{ onClose={() => setAnalyticsModal(false)} cycleDetails={cycleDetails ?? undefined} /> -
-
+ +
{ />
+
+
{ > -
-
+ + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx index f38fe94f2..acaf935bb 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/header.tsx @@ -4,7 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // ui -import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui"; +import { Breadcrumbs, Button, ContrastIcon, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { CyclesViewHeader } from "@/components/cycles"; @@ -30,48 +30,50 @@ export const CyclesListHeader: FC = observer(() => { currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); return ( -
-
-
- - - - - ) - } - /> - } - /> - } />} - /> - -
-
- {canUserCreateCycle && currentProjectDetails && ( -
- - -
- )} -
+ + + + + + + ) + } + /> + } + /> + } />} + /> + + + + {canUserCreateCycle && currentProjectDetails ? ( +
+ + +
+ ) : ( + <> + )} +
+
); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx index 5c046d95c..5dd82c7e1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/page.tsx @@ -6,6 +6,7 @@ import { useParams } from "next/navigation"; // types import { TCycleFilters } from "@plane/types"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { PageHead } from "@/components/core"; import { CyclesView, CycleCreateUpdateModal, CycleAppliedFiltersList } from "@/components/cycles"; import { EmptyState } from "@/components/empty-state"; @@ -81,13 +82,13 @@ const ProjectCyclesPage = observer(() => { ) : ( <> {calculateTotalFilters(currentProjectFilters ?? {}) !== 0 && ( -
+ clearAllFilters(projectId.toString())} handleRemoveFilter={handleRemoveFilter} /> -
+ )} diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx index 9d977116d..6fafde56b 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/inbox/header.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { RefreshCcw } from "lucide-react"; // ui -import { Breadcrumbs, Button, Intake } from "@plane/ui"; +import { Breadcrumbs, Button, Intake, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { InboxIssueCreateEditModalRoot } from "@/components/inbox"; @@ -30,8 +30,8 @@ export const ProjectInboxHeader: FC = observer(() => { const isViewer = currentProjectRole === EUserProjectRoles.VIEWER; return ( -
-
+ +
{
)}
-
+ + + {currentProjectDetails?.inbox_view && workspaceSlug && projectId && !isViewer ? ( +
+ setCreateIssueModal(false)} + issue={undefined} + /> - {currentProjectDetails?.inbox_view && workspaceSlug && projectId && !isViewer && ( -
- setCreateIssueModal(false)} - issue={undefined} - /> - - -
- )} -
+ +
+ ) : ( + <> + )} + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx index 9f0747cce..067424ffc 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/header.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { PanelRight } from "lucide-react"; // ui -import { Breadcrumbs, LayersIcon } from "@plane/ui"; +import { Breadcrumbs, LayersIcon, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { IssueDetailQuickActions } from "@/components/issues"; @@ -29,8 +29,8 @@ export const ProjectIssueDetailsHeader = observer(() => { const isSidebarCollapsed = issueDetailSidebarCollapsed; return ( -
-
+ +
{ />
-
- - -
+ + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx index 3ff159244..7037e03ad 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(list)/header.tsx @@ -1,248 +1,131 @@ "use client"; -import { useCallback, useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // icons import { Briefcase, Circle, ExternalLink } from "lucide-react"; -// types -import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui -import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui"; +import { Breadcrumbs, Button, LayersIcon, Tooltip, CustomHeader } from "@plane/ui"; // components -import { ProjectAnalyticsModal } from "@/components/analytics"; import { BreadcrumbLink, CountChip, Logo } from "@/components/common"; -import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; // constants -import { - EIssueFilterType, - EIssuesStoreType, - EIssueLayoutTypes, - ISSUE_DISPLAY_FILTERS_BY_LAYOUT, -} from "@/constants/issue"; -import { EUserProjectRoles } from "@/constants/project"; +import HeaderFilters from "@/components/issues/filters"; +import { EIssuesStoreType } from "@/constants/issue"; // helpers +import { EUserProjectRoles } from "@/constants/project"; import { SPACE_BASE_PATH, SPACE_BASE_URL } from "@/helpers/common.helper"; -import { isIssueFilterActive } from "@/helpers/filter.helper"; // hooks -import { - useEventTracker, - useLabel, - useProject, - useProjectState, - useUser, - useMember, - useCommandPalette, -} from "@/hooks/store"; +import { useCommandPalette, useEventTracker, useProject, useUser } from "@/hooks/store"; import { useIssues } from "@/hooks/store/use-issues"; import { useAppRouter } from "@/hooks/use-app-router"; import { usePlatformOS } from "@/hooks/use-platform-os"; export const ProjectIssuesHeader = observer(() => { - // states - const [analyticsModal, setAnalyticsModal] = useState(false); // router const router = useAppRouter(); const { workspaceSlug, projectId } = useParams() as { workspaceSlug: string; projectId: string }; // store hooks - const { - project: { projectMemberIds }, - } = useMember(); - const { - issuesFilter: { issueFilters, updateFilters }, - issues: { getGroupIssueCount }, - } = useIssues(EIssuesStoreType.PROJECT); - const { toggleCreateIssueModal } = useCommandPalette(); - const { setTrackElement } = useEventTracker(); const { membership: { currentProjectRole }, } = useUser(); + const { + issues: { getGroupIssueCount }, + } = useIssues(EIssuesStoreType.PROJECT); + const { currentProjectDetails, loader } = useProject(); - const { projectStates } = useProjectState(); - const { projectLabels } = useLabel(); + + const { toggleCreateIssueModal } = useCommandPalette(); + const { setTrackElement } = useEventTracker(); const { isMobile } = usePlatformOS(); - const activeLayout = issueFilters?.displayFilters?.layout; - const handleFiltersUpdate = useCallback( - (key: keyof IIssueFilterOptions, value: string | string[]) => { - if (!workspaceSlug || !projectId) return; - const newValues = issueFilters?.filters?.[key] ?? []; - - if (Array.isArray(value)) { - // this validation is majorly for the filter start_date, target_date custom - value.forEach((val) => { - if (!newValues.includes(val)) newValues.push(val); - else newValues.splice(newValues.indexOf(val), 1); - }); - } else { - if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); - else newValues.push(value); - } - - updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }); - }, - [workspaceSlug, projectId, issueFilters, updateFilters] - ); - - const handleLayoutChange = useCallback( - (layout: EIssueLayoutTypes) => { - if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }); - }, - [workspaceSlug, projectId, updateFilters] - ); - - const handleDisplayFilters = useCallback( - (updatedDisplayFilter: Partial) => { - if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter); - }, - [workspaceSlug, projectId, updateFilters] - ); - - const handleDisplayProperties = useCallback( - (property: Partial) => { - if (!workspaceSlug || !projectId) return; - updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property); - }, - [workspaceSlug, projectId, updateFilters] - ); const SPACE_APP_URL = (SPACE_BASE_URL.trim() === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH; const publishedURL = `${SPACE_APP_URL}/issues/${currentProjectDetails?.anchor}`; + const issuesCount = getGroupIssueCount(undefined, undefined, false); const canUserCreateIssue = currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); - const issuesCount = getGroupIssueCount(undefined, undefined, false); - return ( - <> - setAnalyticsModal(false)} - projectDetails={currentProjectDetails ?? undefined} - /> - -
-
-
- router.back()} isLoading={loader}> - - - - ) - ) : ( - - + + +
+ router.back()} isLoading={loader}> + + ) - } - /> - } - /> - - } />} - /> - - {issuesCount && issuesCount > 0 ? ( - 1 ? "issues" : "issue"} in this project`} - position="bottom" - > - - - ) : null} -
- {currentProjectDetails?.anchor && ( - - - Public - - - )} -
-
- handleLayoutChange(layout)} - selectedLayout={activeLayout} - /> - - + + + ) + } + /> } - labels={projectLabels} - memberIds={projectMemberIds ?? undefined} - states={projectStates} - cycleViewDisabled={!currentProjectDetails?.cycle_view} - moduleViewDisabled={!currentProjectDetails?.module_view} /> - - - - -
- {canUserCreateIssue && ( - <> - - - + + + ) : null} +
+ {currentProjectDetails?.anchor ? ( + + + Public + + + ) : ( + <> )} -
- + + +
+ +
+ {canUserCreateIssue ? ( + + ) : ( + <> + )} +
+ ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx index 91df88ffb..d35f31465 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx @@ -69,7 +69,7 @@ const ModuleIssuesPage = observer(() => { {moduleId && !isSidebarCollapsed && (
{ onClose={() => setAnalyticsModal(false)} moduleDetails={moduleDetails ?? undefined} /> -
-
-
- - - - - - - ) - } - /> - - + + + + + - ... - + icon={ + currentProjectDetails && ( + + + + ) + } + /> - } - /> - } - /> - } - /> - - -
-

{moduleDetails?.name && moduleDetails.name}

- {issuesCount && issuesCount > 0 ? ( - 1 ? "issues" : "issue" - } in this module`} - position="bottom" - > - - {issuesCount} - - - ) : null} -
- - } - className="ml-1.5 flex-shrink-0" - placement="bottom-start" + - {projectModuleIds?.map((moduleId) => )} - - } - /> -
-
-
-
- handleLayoutChange(layout)} - selectedLayout={activeLayout} - /> - - + + } + /> + } /> - - - + + +
+

{moduleDetails?.name && moduleDetails.name}

+ {issuesCount && issuesCount > 0 ? ( + 1 ? "issues" : "issue" + } in this module`} + position="bottom" + > + + {issuesCount} + + + ) : null} +
+ } - displayFilters={issueFilters?.displayFilters ?? {}} - handleDisplayFiltersUpdate={handleDisplayFilters} - displayProperties={issueFilters?.displayProperties ?? {}} - handleDisplayPropertiesUpdate={handleDisplayProperties} - ignoreGroupedFilters={["module"]} - cycleViewDisabled={!currentProjectDetails?.cycle_view} - moduleViewDisabled={!currentProjectDetails?.module_view} - /> -
-
- - {canUserCreateIssue && ( - <> - - - - )} - +
-
-
+ + {canUserCreateIssue ? ( + <> + + + + ) : ( + <> + )} + + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx index 87747e29f..57b9ad624 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/header.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // ui -import { Breadcrumbs, Button, DiceIcon } from "@plane/ui"; +import { Breadcrumbs, Button, DiceIcon, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { ModuleViewHeader } from "@/components/modules"; @@ -30,8 +30,8 @@ export const ModulesListHeader: React.FC = observer(() => { currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); return ( -
-
+ +
{ />
-
-
+ + - {canUserCreateModule && ( + {canUserCreateModule ? ( + ) : ( + <> )} -
-
+ + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx index f8b474d94..9417016e3 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/modules/(list)/page.tsx @@ -57,19 +57,17 @@ const ProjectModulesPage = observer(() => {
{(calculateTotalFilters(currentProjectFilters ?? {}) !== 0 || currentProjectDisplayFilters?.favorites) && ( -
- clearAllFilters(`${projectId}`)} - handleRemoveFilter={handleRemoveFilter} - handleDisplayFiltersUpdate={(val) => { - if (!projectId) return; - updateDisplayFilters(projectId.toString(), val); - }} - alwaysAllowEditing - /> -
+ clearAllFilters(`${projectId}`)} + handleRemoveFilter={handleRemoveFilter} + handleDisplayFiltersUpdate={(val) => { + if (!projectId) return; + updateDisplayFilters(projectId.toString(), val); + }} + alwaysAllowEditing + /> )}
diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx index 8f255fcbf..0d140e3e1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx @@ -7,7 +7,15 @@ import { FileText } from "lucide-react"; // types import { TLogoProps } from "@plane/types"; // ui -import { Breadcrumbs, EmojiIconPicker, EmojiIconPickerTypes, TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; +import { + Breadcrumbs, + EmojiIconPicker, + EmojiIconPickerTypes, + TOAST_TYPE, + Tooltip, + setToast, + CustomHeader, +} from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { PageEditInformationPopover } from "@/components/pages"; @@ -59,8 +67,8 @@ export const PageDetailsHeader = observer(() => { const pageTitle = getPageName(name); return ( -
-
+ +
{ />
-
- - -
+ + + + + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx index aa2e385e4..2856a22d1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(list)/header.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { useParams, useSearchParams } from "next/navigation"; import { FileText } from "lucide-react"; // ui -import { Breadcrumbs, Button } from "@plane/ui"; +import { Breadcrumbs, Button, CustomHeader } from "@plane/ui"; // helpers import { BreadcrumbLink, Logo } from "@/components/common"; // constants @@ -30,8 +30,8 @@ export const PagesListHeader = observer(() => { currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); return ( -
-
+ +
{ />
-
- {canUserCreatePage && ( -
- -
- )} -
+ + + {canUserCreatePage ? ( +
+ +
+ ) : ( + <> + )} +
+ ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx index 6b6939d13..8f3070f1a 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/settings/header.tsx @@ -5,7 +5,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // ui import { Settings } from "lucide-react"; -import { Breadcrumbs, CustomMenu } from "@plane/ui"; +import { Breadcrumbs, CustomMenu, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; // constants @@ -29,8 +29,8 @@ export const ProjectSettingHeader: FC = observer(() => { const projectMemberInfo = currentProjectRole || EUserProjectRoles.GUEST; return ( -
-
+ +
@@ -84,7 +84,7 @@ export const ProjectSettingHeader: FC = observer(() => { ) )} -
-
+
+
); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx index 364a17794..abaff7eb4 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(detail)/[viewId]/header.tsx @@ -8,7 +8,7 @@ import { Layers, Lock } from "lucide-react"; // types import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui -import { Breadcrumbs, Button, CustomMenu, Tooltip } from "@plane/ui"; +import { Breadcrumbs, Button, CustomMenu, Tooltip, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; @@ -136,8 +136,8 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => { const publishLink = getPublishViewLink(viewDetails?.anchor); return ( -
-
+ + { /> - {viewDetails?.access === EViewAccess.PRIVATE && ( + {viewDetails?.access === EViewAccess.PRIVATE ? (
+ ) : ( + <> )} - {viewDetails?.anchor && publishLink && ( + {viewDetails?.anchor && publishLink ? ( { Live + ) : ( + <> )} -
-
- {!viewDetails?.is_locked && ( + + + {!viewDetails?.is_locked ? ( <> { /> + ) : ( + <> )} - {canUserCreateIssue && ( + {canUserCreateIssue ? ( + ) : ( + <> )} -
-
+ + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx index 27f84d94e..bb91ab7ea 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/header.tsx @@ -1,22 +1,15 @@ "use client"; -import { useCallback } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Layers } from "lucide-react"; // ui -import { TViewFilterProps } from "@plane/types"; -import { Breadcrumbs, Button } from "@plane/ui"; +import { Breadcrumbs, Button, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink, Logo } from "@/components/common"; import { ViewListHeader } from "@/components/views"; -import { ViewAppliedFiltersList } from "@/components/views/applied-filters"; -// constants -import { EViewAccess } from "@/constants/views"; -// helpers -import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useCommandPalette, useProject, useProjectView } from "@/hooks/store"; +import { useCommandPalette, useProject } from "@/hooks/store"; export const ProjectViewsHeader = observer(() => { // router @@ -24,75 +17,43 @@ export const ProjectViewsHeader = observer(() => { // store hooks const { toggleCreateViewModal } = useCommandPalette(); const { currentProjectDetails, loader } = useProject(); - const { filters, updateFilters, clearAllFilters } = useProjectView(); - - const handleRemoveFilter = useCallback( - (key: keyof TViewFilterProps, value: string | EViewAccess | null) => { - let newValues = filters.filters?.[key]; - - if (key === "favorites") { - newValues = !!value; - } - if (Array.isArray(newValues)) { - if (!value) newValues = []; - else newValues = newValues.filter((val) => val !== value) as string[]; - } - - updateFilters("filters", { [key]: newValues }); - }, - [filters.filters, updateFilters] - ); - - const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0; return ( <> -
-
-
- - - - - ) - } - /> - } - /> - } />} - /> - -
-
-
+ + + + + + + ) + } + /> + } + /> + } />} + /> + + +
-
-
- {isFiltersApplied && ( -
- -
- )} + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx index 25daf594c..664f0055f 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/views/(list)/page.tsx @@ -1,21 +1,29 @@ "use client"; +import { useCallback } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components +import { TViewFilterProps } from "@plane/types"; +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { PageHead } from "@/components/core"; import { EmptyState } from "@/components/empty-state"; import { ProjectViewsList } from "@/components/views"; -// constants +import { ViewAppliedFiltersList } from "@/components/views/applied-filters"; import { EmptyStateType } from "@/constants/empty-state"; +// constants +import { EViewAccess } from "@/constants/views"; +// helpers +import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useProject } from "@/hooks/store"; +import { useProject, useProjectView } from "@/hooks/store"; const ProjectViewsPage = observer(() => { // router const { workspaceSlug, projectId } = useParams(); // store const { getProjectById, currentProjectDetails } = useProject(); + const { filters, updateFilters, clearAllFilters } = useProjectView(); // derived values const project = projectId ? getProjectById(projectId.toString()) : undefined; const pageTitle = project?.name ? `${project?.name} - Views` : undefined; @@ -32,10 +40,38 @@ const ProjectViewsPage = observer(() => { />
); + const handleRemoveFilter = useCallback( + (key: keyof TViewFilterProps, value: string | EViewAccess | null) => { + let newValues = filters.filters?.[key]; + + if (key === "favorites") { + newValues = !!value; + } + if (Array.isArray(newValues)) { + if (!value) newValues = []; + else newValues = newValues.filter((val) => val !== value) as string[]; + } + + updateFilters("filters", { [key]: newValues }); + }, + [filters.filters, updateFilters] + ); + + const isFiltersApplied = calculateTotalFilters(filters?.filters ?? {}) !== 0; return ( <> + {isFiltersApplied && ( + + + + )} ); diff --git a/web/app/[workspaceSlug]/(projects)/settings/header.tsx b/web/app/[workspaceSlug]/(projects)/settings/header.tsx index 5407f79bc..3367597cf 100644 --- a/web/app/[workspaceSlug]/(projects)/settings/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/settings/header.tsx @@ -4,7 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { Settings } from "lucide-react"; // ui -import { Breadcrumbs } from "@plane/ui"; +import { Breadcrumbs, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; // hooks @@ -14,24 +14,22 @@ export const WorkspaceSettingHeader: FC = observer(() => { const { currentWorkspace, loader } = useWorkspace(); return ( -
-
-
- - } - /> - } - /> - } /> - -
-
-
+ + + + } + /> + } + /> + } /> + + + ); }); diff --git a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx index 82e11b208..d45459b98 100644 --- a/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/workspace-views/header.tsx @@ -7,7 +7,7 @@ import { useParams } from "next/navigation"; import { Layers } from "lucide-react"; import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; // ui -import { Breadcrumbs, Button } from "@plane/ui"; +import { Breadcrumbs, Button, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "@/components/issues"; @@ -98,17 +98,18 @@ export const GlobalIssuesHeader = observer(() => { return ( <> setCreateViewModal(false)} /> -
-
+ + } />} /> -
-
- {!isLocked && ( + + + + {!isLocked ? ( <> { /> + ) : ( + <> )} -
-
+ + ); }); diff --git a/web/core/components/common/breadcrumb-link.tsx b/web/core/components/common/breadcrumb-link.tsx index c04888790..a78a60972 100644 --- a/web/core/components/common/breadcrumb-link.tsx +++ b/web/core/components/common/breadcrumb-link.tsx @@ -25,7 +25,7 @@ export const BreadcrumbLink: React.FC = (props) => { href={href} > {icon && ( -
{icon}
+
{icon}
)} {label && (
{label}
diff --git a/web/core/components/core/app-header.tsx b/web/core/components/core/app-header.tsx index 05f7ab4c1..05c28ac5a 100644 --- a/web/core/components/core/app-header.tsx +++ b/web/core/components/core/app-header.tsx @@ -2,6 +2,7 @@ import { ReactNode } from "react"; // components +import { CustomRow } from "@plane/ui"; import { SidebarHamburgerToggle } from "@/components/core"; export interface AppHeaderProps { @@ -13,16 +14,14 @@ export const AppHeader = (props: AppHeaderProps) => { const { header, mobileHeader } = props; return ( - <> -
-
-
- -
-
{header}
+
+ +
+
- {mobileHeader && mobileHeader} -
- +
{header}
+ + {mobileHeader && mobileHeader} +
); }; diff --git a/web/core/components/inbox/content/inbox-issue-mobile-header.tsx b/web/core/components/inbox/content/inbox-issue-mobile-header.tsx index dd63c3bf9..4533e079a 100644 --- a/web/core/components/inbox/content/inbox-issue-mobile-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-mobile-header.tsx @@ -15,7 +15,7 @@ import { PanelLeft, MoveRight, } from "lucide-react"; -import { CustomMenu } from "@plane/ui"; +import { CustomHeader, CustomMenu, EHeaderVariant } from "@plane/ui"; // components import { InboxIssueStatus } from "@/components/inbox"; import { IssueUpdateStatus } from "@/components/issues"; @@ -80,7 +80,7 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = if (!issue || !inboxIssue) return null; return ( -
+ {isNotificationEmbed && (
-
+ ); }); diff --git a/web/core/components/inbox/inbox-filter/applied-filters/root.tsx b/web/core/components/inbox/inbox-filter/applied-filters/root.tsx index 834d6a22c..8896a3608 100644 --- a/web/core/components/inbox/inbox-filter/applied-filters/root.tsx +++ b/web/core/components/inbox/inbox-filter/applied-filters/root.tsx @@ -1,6 +1,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { InboxIssueAppliedFiltersStatus, InboxIssueAppliedFiltersPriority, @@ -17,7 +18,7 @@ export const InboxIssueAppliedFilters: FC = observer(() => { if (getAppliedFiltersCount === 0) return <>; return ( -
+ {/* status */} {/* state */} @@ -34,6 +35,6 @@ export const InboxIssueAppliedFilters: FC = observer(() => { {/* updated_at */} -
+ ); }); diff --git a/web/core/components/inbox/inbox-filter/root.tsx b/web/core/components/inbox/inbox-filter/root.tsx index b3a2dc2c4..4a86e45de 100644 --- a/web/core/components/inbox/inbox-filter/root.tsx +++ b/web/core/components/inbox/inbox-filter/root.tsx @@ -1,13 +1,33 @@ import { FC } from "react"; -import { ListFilter } from "lucide-react"; +import { ChevronDown, ListFilter } from "lucide-react"; // components +import { cn } from "@plane/editor"; +import { getButtonStyling } from "@plane/ui"; import { InboxIssueFilterSelection, InboxIssueOrderByDropdown } from "@/components/inbox/inbox-filter"; import { FiltersDropdown } from "@/components/issues"; +const smallButton = ; +const largeButton = ( +
+ + Filters + + +
+); export const FiltersRoot: FC = () => (
- } title="Filters" placement="bottom-end"> + +
{largeButton}
+
{smallButton}
+ + } + title="" + placement="bottom-end" + >
diff --git a/web/core/components/inbox/inbox-filter/sorting/order-by.tsx b/web/core/components/inbox/inbox-filter/sorting/order-by.tsx index 93786c0ab..fda6ce5a2 100644 --- a/web/core/components/inbox/inbox-filter/sorting/order-by.tsx +++ b/web/core/components/inbox/inbox-filter/sorting/order-by.tsx @@ -16,19 +16,27 @@ export const InboxIssueOrderByDropdown: FC = observer(() => { const { inboxSorting, handleInboxIssueSorting } = useProjectInbox(); const orderByDetails = INBOX_ISSUE_ORDER_BY_OPTIONS.find((option) => inboxSorting?.order_by?.includes(option.key)) || undefined; + const smallButton = + inboxSorting?.sort_by === "asc" ? : ; + const largeButton = ( +
+ {inboxSorting?.sort_by === "asc" ? ( + + ) : ( + + )} + {orderByDetails?.label || "Order By"} + +
+ ); return ( - {inboxSorting?.sort_by === "asc" ? ( - - ) : ( - - )} - {orderByDetails?.label || "Order By"} - -
+ <> +
{largeButton}
+
{smallButton}
+ } placement="bottom-end" maxHeight="lg" diff --git a/web/core/components/inbox/sidebar/root.tsx b/web/core/components/inbox/sidebar/root.tsx index 58f49a46c..79d271933 100644 --- a/web/core/components/inbox/sidebar/root.tsx +++ b/web/core/components/inbox/sidebar/root.tsx @@ -3,7 +3,7 @@ import { FC, useCallback, useEffect, useRef, useState } from "react"; import { observer } from "mobx-react"; import { TInboxIssueCurrentTab } from "@plane/types"; -import { Loader } from "@plane/ui"; +import { CustomHeader, Loader, EHeaderVariant } from "@plane/ui"; // components import { EmptyState } from "@/components/empty-state"; import { FiltersRoot, InboxIssueAppliedFilters, InboxIssueList } from "@/components/inbox"; @@ -76,7 +76,7 @@ export const InboxSidebar: FC = observer((props) => { return (
-
+ {tabNavigationOptions.map((option) => (
= observer((props) => { />
))} -
+
-
- +
{loader != undefined && loader === "filter-loading" && !inboxIssuePaginationInfo?.next_page_results ? ( diff --git a/web/core/components/issues/filters.tsx b/web/core/components/issues/filters.tsx new file mode 100644 index 000000000..a270982ee --- /dev/null +++ b/web/core/components/issues/filters.tsx @@ -0,0 +1,143 @@ +"use client"; + +import { useCallback, useState } from "react"; +import { TProject } from "ee/types"; +// types +import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types"; +// ui +import { Button } from "@plane/ui"; + +import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues"; +// constants +import { + EIssueFilterType, + EIssuesStoreType, + EIssueLayoutTypes, + ISSUE_DISPLAY_FILTERS_BY_LAYOUT, +} from "@/constants/issue"; +// helpers +import { isIssueFilterActive } from "@/helpers/filter.helper"; +// hooks +import { useLabel, useProjectState, useMember, useIssues } from "@/hooks/store"; +import { ProjectAnalyticsModal } from "../analytics"; + +type Props = { + currentProjectDetails: TProject | undefined; + projectId: string; + workspaceSlug: string; + canUserCreateIssue: boolean | undefined; +}; +const HeaderFilters = ({ currentProjectDetails, projectId, workspaceSlug, canUserCreateIssue }: Props) => { + // states + const [analyticsModal, setAnalyticsModal] = useState(false); + // store hooks + const { + project: { projectMemberIds }, + } = useMember(); + const { + issuesFilter: { issueFilters, updateFilters }, + } = useIssues(EIssuesStoreType.PROJECT); + + const { projectStates } = useProjectState(); + const { projectLabels } = useLabel(); + const activeLayout = issueFilters?.displayFilters?.layout; + + const handleFiltersUpdate = useCallback( + (key: keyof IIssueFilterOptions, value: string | string[]) => { + if (!workspaceSlug || !projectId) return; + const newValues = issueFilters?.filters?.[key] ?? []; + + if (Array.isArray(value)) { + // this validation is majorly for the filter start_date, target_date custom + value.forEach((val) => { + if (!newValues.includes(val)) newValues.push(val); + else newValues.splice(newValues.indexOf(val), 1); + }); + } else { + if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else newValues.push(value); + } + + updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { [key]: newValues }); + }, + [workspaceSlug, projectId, issueFilters, updateFilters] + ); + const handleLayoutChange = useCallback( + (layout: EIssueLayoutTypes) => { + if (!workspaceSlug || !projectId) return; + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, { layout: layout }); + }, + [workspaceSlug, projectId, updateFilters] + ); + + const handleDisplayFilters = useCallback( + (updatedDisplayFilter: Partial) => { + if (!workspaceSlug || !projectId) return; + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter); + }, + [workspaceSlug, projectId, updateFilters] + ); + + const handleDisplayProperties = useCallback( + (property: Partial) => { + if (!workspaceSlug || !projectId) return; + updateFilters(workspaceSlug, projectId, EIssueFilterType.DISPLAY_PROPERTIES, property); + }, + [workspaceSlug, projectId, updateFilters] + ); + + return ( + <> + setAnalyticsModal(false)} + projectDetails={currentProjectDetails ?? undefined} + /> + handleLayoutChange(layout)} + selectedLayout={activeLayout} + /> + + + + + + + {canUserCreateIssue ? ( + + ) : ( + <> + )} + + ); +}; + +export default HeaderFilters; diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx index 1227d0d4a..82663a2c4 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx @@ -59,7 +59,7 @@ export const AppliedFiltersList: React.FC = observer((props) => { !disableEditing && (alwaysAllowEditing || (currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER)); return ( -
+
{Object.entries(appliedFilters).map(([key, value]) => { const filterKey = key as keyof IIssueFilterOptions; diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx index f51a2d7fa..504dae3cb 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/cycle-root.tsx @@ -2,6 +2,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { IIssueFilterOptions } from "@plane/types"; // hooks +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { AppliedFiltersList, SaveFilterView } from "@/components/issues"; import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue"; import { useIssues, useLabel, useProjectState } from "@/hooks/store"; @@ -76,15 +77,16 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => { if (Object.keys(appliedFilters).length === 0 || !workspaceSlug || !projectId || !cycleId) return null; return ( -
- - + + + + { display_properties: issueFilters?.displayProperties, }} /> -
+ ); }); diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx index b898122e9..bcb26eafb 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/global-view-root.tsx @@ -6,10 +6,10 @@ import isEmpty from "lodash/isEmpty"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // types -import { cn } from "@plane/editor"; import { IIssueFilterOptions, TStaticViewTypes } from "@plane/types"; //ui // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { AppliedFiltersList } from "@/components/issues"; import { UpdateViewComponent } from "@/components/views/update-view-component"; import { CreateUpdateWorkspaceViewModal } from "@/components/workspace"; @@ -133,7 +133,7 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => { if (areAppliedFiltersEmpty && areFiltersEqual) return null; return ( - <> + setIsModalOpen(false)} @@ -144,31 +144,28 @@ export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => { ...viewFilters, }} /> -
- - {!isDefaultView && ( - - )} -
- + + + {!isDefaultView ? ( + + ) : ( + <> + )} +
); }); diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx index 92e9878d2..fe35d2b1e 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/module-root.tsx @@ -2,6 +2,7 @@ import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { IIssueFilterOptions } from "@plane/types"; // hooks +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { AppliedFiltersList, SaveFilterView } from "@/components/issues"; import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue"; import { useIssues, useLabel, useProjectState } from "@/hooks/store"; @@ -75,15 +76,16 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => { if (!workspaceSlug || !projectId || !moduleId || Object.keys(appliedFilters).length === 0) return null; return ( -
- - + + + + { display_properties: issueFilters?.displayProperties, }} /> -
+ ); }); diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx index 56ab5f37c..aaafa5e46 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx @@ -3,6 +3,7 @@ import { useParams } from "next/navigation"; import { IIssueFilterOptions } from "@plane/types"; // hooks // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { AppliedFiltersList, SaveFilterView } from "@/components/issues"; // constants import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue"; @@ -67,7 +68,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => { if (Object.keys(appliedFilters).length === 0) return null; return ( -
+ { }} /> )} -
+ ); }); diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx index 9772d7c27..ba21fa126 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx @@ -8,6 +8,7 @@ import { useParams } from "next/navigation"; // types import { IIssueFilterOptions } from "@plane/types"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { AppliedFiltersList } from "@/components/issues"; import { CreateUpdateProjectViewModal } from "@/components/views"; import { UpdateViewComponent } from "@/components/views/update-view-component"; @@ -113,7 +114,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { const isOwner = viewDetails?.owned_by === data?.id; return ( -
+ setIsModalOpen(false)} @@ -127,7 +128,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => { ...viewFilters, }} /> -
+ { states={projectStates} disableEditing={isLocked} /> -
+ { setIsModalOpen={setIsModalOpen} handleUpdateView={handleUpdateView} /> -
+ ); }); diff --git a/web/core/components/modules/applied-filters/root.tsx b/web/core/components/modules/applied-filters/root.tsx index e48b8562f..fedc1a800 100644 --- a/web/core/components/modules/applied-filters/root.tsx +++ b/web/core/components/modules/applied-filters/root.tsx @@ -1,6 +1,7 @@ import { X } from "lucide-react"; import { TModuleDisplayFilters, TModuleFilters } from "@plane/types"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { AppliedDateFilters, AppliedMembersFilters, AppliedStatusFilters } from "@/components/modules"; // helpers import { replaceUnderscoreIfSnakeCase } from "@/helpers/string.helper"; @@ -36,91 +37,93 @@ export const ModuleAppliedFiltersList: React.FC = (props) => { const isEditingAllowed = alwaysAllowEditing; return ( -
- {Object.entries(appliedFilters).map(([key, value]) => { - const filterKey = key as keyof TModuleFilters; + + + {Object.entries(appliedFilters).map(([key, value]) => { + const filterKey = key as keyof TModuleFilters; - if (!value) return; - if (Array.isArray(value) && value.length === 0) return; + if (!value) return; + if (Array.isArray(value) && value.length === 0) return; - return ( + return ( +
+
+ {replaceUnderscoreIfSnakeCase(filterKey)} + {filterKey === "status" && ( + handleRemoveFilter("status", val)} + values={value} + /> + )} + {DATE_FILTERS.includes(filterKey) && ( + handleRemoveFilter(filterKey, val)} + values={value} + /> + )} + {MEMBERS_FILTERS.includes(filterKey) && ( + handleRemoveFilter(filterKey, val)} + values={value} + /> + )} + {isEditingAllowed && ( + + )} +
+
+ ); + })} + {!isArchived && isFavoriteFilterApplied && (
- {replaceUnderscoreIfSnakeCase(filterKey)} - {filterKey === "status" && ( - handleRemoveFilter("status", val)} - values={value} - /> - )} - {DATE_FILTERS.includes(filterKey) && ( - handleRemoveFilter(filterKey, val)} - values={value} - /> - )} - {MEMBERS_FILTERS.includes(filterKey) && ( - handleRemoveFilter(filterKey, val)} - values={value} - /> - )} - {isEditingAllowed && ( - - )} + Modules +
+ Favorite + {isEditingAllowed && ( + + )} +
- ); - })} - {!isArchived && isFavoriteFilterApplied && ( -
-
- Modules -
- Favorite - {isEditingAllowed && ( - - )} -
-
-
- )} - {isEditingAllowed && ( - - )} -
+ )} + {isEditingAllowed && ( + + )} + + ); }; diff --git a/web/core/components/pages/editor/header/extra-options.tsx b/web/core/components/pages/editor/header/extra-options.tsx index b0493fc30..bd955ef4f 100644 --- a/web/core/components/pages/editor/header/extra-options.tsx +++ b/web/core/components/pages/editor/header/extra-options.tsx @@ -44,7 +44,7 @@ export const PageExtraOptions: React.FC = observer((props) => { }; return ( -
+
{is_locked && } {archived_at && (
diff --git a/web/core/components/pages/editor/header/mobile-root.tsx b/web/core/components/pages/editor/header/mobile-root.tsx index a1ba8ef6f..e63bd96bc 100644 --- a/web/core/components/pages/editor/header/mobile-root.tsx +++ b/web/core/components/pages/editor/header/mobile-root.tsx @@ -1,6 +1,7 @@ import { observer } from "mobx-react"; import { EditorReadOnlyRefApi, EditorRefApi, IMarking } from "@plane/editor"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { PageExtraOptions, PageSummaryPopover, PageToolbar } from "@/components/pages"; // hooks import { usePageFilters } from "@/hooks/use-page-filters"; @@ -42,8 +43,8 @@ export const PageEditorMobileHeaderRoot: React.FC = observer((props) => { return ( <> -
-
+ +
= observer((props) => { page={page} readOnlyEditorRef={readOnlyEditorRef} /> -
-
+ + {(editorReady || readOnlyEditorReady) && isContentEditable && editorRef.current && ( )} -
+
); }); diff --git a/web/core/components/pages/editor/header/root.tsx b/web/core/components/pages/editor/header/root.tsx index f02070f52..2428448d8 100644 --- a/web/core/components/pages/editor/header/root.tsx +++ b/web/core/components/pages/editor/header/root.tsx @@ -1,6 +1,7 @@ import { observer } from "mobx-react"; import { EditorReadOnlyRefApi, EditorRefApi, IMarking } from "@plane/editor"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { PageEditorMobileHeaderRoot, PageExtraOptions, PageSummaryPopover, PageToolbar } from "@/components/pages"; // helpers import { cn } from "@/helpers/common.helper"; @@ -44,13 +45,8 @@ export const PageEditorHeaderRoot: React.FC = observer((props) => { return ( <> -
-
+ +
= observer((props) => { page={page} readOnlyEditorRef={readOnlyEditorRef} /> -
+
= observer((props) => { return ( <> -
- -
+ + + + + updateFilters("searchQuery", val)} @@ -76,17 +79,17 @@ export const PagesListHeaderRoot: React.FC = observer((props) => { memberIds={workspaceMemberIds ?? undefined} /> -
-
+ + {calculateTotalFilters(filters?.filters ?? {}) !== 0 && ( -
+ -
+ )} ); diff --git a/web/core/components/pages/list/search-input.tsx b/web/core/components/pages/list/search-input.tsx index ea31617d3..8f677405b 100644 --- a/web/core/components/pages/list/search-input.tsx +++ b/web/core/components/pages/list/search-input.tsx @@ -36,11 +36,11 @@ export const PageSearchInput: FC = (props) => { }, [searchQuery]); return ( - <> +
{!isSearchOpen && ( )} -
= (props) => { )}
- +
); }; diff --git a/web/core/components/project/applied-filters/root.tsx b/web/core/components/project/applied-filters/root.tsx index d7f40e137..58aa5a8ac 100644 --- a/web/core/components/project/applied-filters/root.tsx +++ b/web/core/components/project/applied-filters/root.tsx @@ -47,7 +47,7 @@ export const ProjectAppliedFiltersList: React.FC = (props) => { const isEditingAllowed = alwaysAllowEditing; return ( -
+
{/* Applied filters */} {Object.entries(appliedFilters ?? {}).map(([key, value]) => { @@ -132,7 +132,7 @@ export const ProjectAppliedFiltersList: React.FC = (props) => {

} > - + {filteredProjects}/{totalProjects} diff --git a/web/core/components/project/filters.tsx b/web/core/components/project/filters.tsx new file mode 100644 index 000000000..25a7ab834 --- /dev/null +++ b/web/core/components/project/filters.tsx @@ -0,0 +1,93 @@ +import { useCallback } from "react"; +import { useParams } from "next/navigation"; +import { ListFilter } from "lucide-react"; +// types +import { cn } from "@plane/editor"; +import { TProjectFilters } from "@plane/types"; +// components +import { FiltersDropdown } from "@/components/issues"; +import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project"; +// helpers +import { calculateTotalFilters } from "@/helpers/filter.helper"; +// hooks +import { useMember, useProjectFilter } from "@/hooks/store"; + +type Props = { + filterMenuButton?: React.ReactNode; + classname?: string; + filterClassname?: string; + isMobile?: boolean; +}; + +const HeaderFilters = ({ filterMenuButton, isMobile, classname = "", filterClassname = "" }: Props) => { + // router + const { workspaceSlug } = useParams(); + const { + currentWorkspaceDisplayFilters: displayFilters, + currentWorkspaceFilters: filters, + updateFilters, + updateDisplayFilters, + } = useProjectFilter(); + const { + workspace: { workspaceMemberIds }, + } = useMember(); + const handleFilters = useCallback( + (key: keyof TProjectFilters, value: string | string[]) => { + if (!workspaceSlug) return; + let newValues = filters?.[key] ?? []; + if (Array.isArray(value)) { + if (key === "created_at" && newValues.find((v) => v.includes("custom"))) newValues = []; + value.forEach((val) => { + if (!newValues.includes(val)) newValues.push(val); + else newValues.splice(newValues.indexOf(val), 1); + }); + } else { + if (filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); + else { + if (key === "created_at") newValues = [value]; + else newValues.push(value); + } + } + + updateFilters(workspaceSlug.toString(), { [key]: newValues }); + }, + [filters, updateFilters, workspaceSlug] + ); + const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0; + + return ( +
+ { + if (!workspaceSlug || val === displayFilters?.order_by) return; + updateDisplayFilters(workspaceSlug.toString(), { + order_by: val, + }); + }} + isMobile={isMobile} + /> +
+ } + title="Filters" + placement="bottom-end" + isFiltersApplied={isFiltersApplied} + menuButton={filterMenuButton || null} + > + { + if (!workspaceSlug) return; + updateDisplayFilters(workspaceSlug.toString(), val); + }} + memberIds={workspaceMemberIds ?? undefined} + /> + +
+
+ ); +}; +export default HeaderFilters; diff --git a/web/core/components/project/header.tsx b/web/core/components/project/header.tsx index 4c78030b3..9ef75b7a8 100644 --- a/web/core/components/project/header.tsx +++ b/web/core/components/project/header.tsx @@ -1,29 +1,23 @@ "use client"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { observer } from "mobx-react"; -import { useParams, usePathname } from "next/navigation"; -import { Search, Briefcase, X, ListFilter } from "lucide-react"; -// types -import { TProjectFilters } from "@plane/types"; +import { usePathname } from "next/navigation"; +import { Search, Briefcase, X } from "lucide-react"; // ui -import { Breadcrumbs, Button } from "@plane/ui"; +import { Breadcrumbs, Button, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; -import { FiltersDropdown } from "@/components/issues"; -import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project"; // constants import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers import { cn } from "@/helpers/common.helper"; -import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks -import { useCommandPalette, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store"; +import { useCommandPalette, useEventTracker, useProjectFilter, useUser } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; +import HeaderFilters from "./filters"; export const ProjectsBaseHeader = observer(() => { - // router - const { workspaceSlug } = useParams(); // states const [isSearchOpen, setIsSearchOpen] = useState(false); // refs @@ -36,17 +30,8 @@ export const ProjectsBaseHeader = observer(() => { } = useUser(); const pathname = usePathname(); - const { - currentWorkspaceDisplayFilters: displayFilters, - currentWorkspaceFilters: filters, - updateFilters, - updateDisplayFilters, - searchQuery, - updateSearchQuery, - } = useProjectFilter(); - const { - workspace: { workspaceMemberIds }, - } = useMember(); + const { searchQuery, updateSearchQuery } = useProjectFilter(); + // outside click detector hook useOutsideClickDetector(inputRef, () => { if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false); @@ -55,29 +40,6 @@ export const ProjectsBaseHeader = observer(() => { const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; const isArchived = pathname.includes("/archives"); - const handleFilters = useCallback( - (key: keyof TProjectFilters, value: string | string[]) => { - if (!workspaceSlug) return; - let newValues = filters?.[key] ?? []; - if (Array.isArray(value)) { - if (key === "created_at" && newValues.find((v) => v.includes("custom"))) newValues = []; - value.forEach((val) => { - if (!newValues.includes(val)) newValues.push(val); - else newValues.splice(newValues.indexOf(val), 1); - }); - } else { - if (filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); - else { - if (key === "created_at") newValues = [value]; - else newValues.push(value); - } - } - - updateFilters(workspaceSlug.toString(), { [key]: newValues }); - }, - [filters, updateFilters, workspaceSlug] - ); - const handleInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Escape") { if (searchQuery && searchQuery.trim() !== "") updateSearchQuery(""); @@ -89,22 +51,18 @@ export const ProjectsBaseHeader = observer(() => { if (searchQuery.trim() !== "") setIsSearchOpen(true); }, [searchQuery]); - const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0; - return ( -
-
-
- - } />} - /> - {isArchived && } />} - -
-
-
+ + + + } />} + /> + {isArchived && } />} + + +
{!isSearchOpen && (
-
- { - if (!workspaceSlug || val === displayFilters?.order_by) return; - updateDisplayFilters(workspaceSlug.toString(), { - order_by: val, - }); - }} - /> - } - title="Filters" - placement="bottom-end" - isFiltersApplied={isFiltersApplied} - > - { - if (!workspaceSlug) return; - updateDisplayFilters(workspaceSlug.toString(), val); - }} - memberIds={workspaceMemberIds ?? undefined} - /> - + +
+
- {isAuthorizedUser && !isArchived && ( + {isAuthorizedUser && !isArchived ? ( + ) : ( + <> )} -
-
+ + ); }); diff --git a/web/core/components/project/root.tsx b/web/core/components/project/root.tsx index 744b248de..87e9a0357 100644 --- a/web/core/components/project/root.tsx +++ b/web/core/components/project/root.tsx @@ -6,6 +6,7 @@ import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import { TProjectAppliedDisplayFilterKeys, TProjectFilters } from "@plane/types"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { PageHead } from "@/components/core"; import { ProjectAppliedFiltersList, ProjectCardList } from "@/components/project"; // helpers @@ -32,9 +33,8 @@ const Root = observer(() => { const isArchived = pathname.includes("/archives"); - const allowedDisplayFilters = currentWorkspaceAppliedDisplayFilters?.filter( - (filter) => filter !== "archived_projects" - ) ?? []; + const allowedDisplayFilters = + currentWorkspaceAppliedDisplayFilters?.filter((filter) => filter !== "archived_projects") ?? []; const handleRemoveFilter = useCallback( (key: keyof TProjectFilters, value: string | null) => { @@ -65,17 +65,17 @@ const Root = observer(() => { }, [clearAllFilters, clearAllAppliedDisplayFilters, workspaceSlug]); useEffect(() => { - isArchived ? updateDisplayFilters(workspaceSlug.toString(), { archived_projects: true }) : - updateDisplayFilters(workspaceSlug.toString(), { archived_projects: false }); + isArchived + ? updateDisplayFilters(workspaceSlug.toString(), { archived_projects: true }) + : updateDisplayFilters(workspaceSlug.toString(), { archived_projects: false }); }, [pathname]); return ( <>
- {(calculateTotalFilters(currentWorkspaceFilters ?? {}) !== 0 || - (allowedDisplayFilters.length>0)) && ( -
+ {(calculateTotalFilters(currentWorkspaceFilters ?? {}) !== 0 || allowedDisplayFilters.length > 0) && ( + { totalProjects={totalProjectIds?.length ?? 0} alwaysAllowEditing /> -
+ )}
diff --git a/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx b/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx index f06aebb1f..93de3d023 100644 --- a/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx +++ b/web/core/components/ui/loader/layouts/spreadsheet-layout-loader.tsx @@ -21,7 +21,7 @@ export const SpreadsheetIssueRowLoader = (props: { columnCount: number }) => ( ); export const SpreadsheetLayoutLoader = () => ( -
+
diff --git a/web/core/components/workspace-notifications/sidebar/filters/applied-filter.tsx b/web/core/components/workspace-notifications/sidebar/filters/applied-filter.tsx index babf99ba7..61003a83c 100644 --- a/web/core/components/workspace-notifications/sidebar/filters/applied-filter.tsx +++ b/web/core/components/workspace-notifications/sidebar/filters/applied-filter.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { X } from "lucide-react"; // constants +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { ENotificationFilterType, FILTER_TYPE_OPTIONS } from "@/constants/notification"; // hooks import { useWorkspaceNotifications } from "@/hooks/store"; @@ -35,30 +36,32 @@ export const AppliedFilters: FC = observer((props) => { if (!isFiltersEnabled || !workspaceSlug) return <>; return ( -
- {FILTER_TYPE_OPTIONS.map((filter) => { - const isSelected = filters?.type?.[filter?.value] || false; - if (!isSelected) return <>; - return ( -
handleFilterTypeChange(filter?.value, !isSelected)} - > -
{filter.label}
-
- + + + {FILTER_TYPE_OPTIONS.map((filter) => { + const isSelected = filters?.type?.[filter?.value] || false; + if (!isSelected) return <>; + return ( +
handleFilterTypeChange(filter?.value, !isSelected)} + > +
{filter.label}
+
+ +
-
- ); - })} + ); + })} -
-
Clear all
-
-
+
+
Clear all
+
+ + ); }); diff --git a/web/core/components/workspace-notifications/sidebar/header/root.tsx b/web/core/components/workspace-notifications/sidebar/header/root.tsx index dd6a36cf8..05cd78e15 100644 --- a/web/core/components/workspace-notifications/sidebar/header/root.tsx +++ b/web/core/components/workspace-notifications/sidebar/header/root.tsx @@ -3,7 +3,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { Inbox } from "lucide-react"; -import { Breadcrumbs } from "@plane/ui"; +import { Breadcrumbs, CustomHeader } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; import { SidebarHamburgerToggle } from "@/components/core"; @@ -18,8 +18,8 @@ export const NotificationSidebarHeader: FC = observe if (!workspaceSlug) return <>; return ( -
-
+ +
@@ -31,9 +31,10 @@ export const NotificationSidebarHeader: FC = observe } /> -
- - -
+ + + + + ); }); diff --git a/web/core/components/workspace-notifications/sidebar/root.tsx b/web/core/components/workspace-notifications/sidebar/root.tsx index 26ad1d76a..753826d3f 100644 --- a/web/core/components/workspace-notifications/sidebar/root.tsx +++ b/web/core/components/workspace-notifications/sidebar/root.tsx @@ -4,6 +4,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // components +import { CustomHeader, CustomRow, EHeaderVariant, ERowVariant } from "@plane/ui"; import { CountChip } from "@/components/common"; import { NotificationsLoader, @@ -46,15 +47,15 @@ export const NotificationsSidebar: FC = observer(() => { )} >
-
+ -
+ -
+ {NOTIFICATION_TABS.map((tab) => (
currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)} >
{ )}
))} -
+
{/* applied filters */} -
- -
+ {/* rendering notifications */} {loader === "init-loader" ? ( diff --git a/web/core/components/workspace/views/header.tsx b/web/core/components/workspace/views/header.tsx index e3ec56bab..81d450186 100644 --- a/web/core/components/workspace/views/header.tsx +++ b/web/core/components/workspace/views/header.tsx @@ -6,6 +6,7 @@ import { Plus } from "lucide-react"; // types import { TStaticViewTypes } from "@plane/types"; // components +import { CustomHeader, EHeaderVariant } from "@plane/ui"; import { CreateUpdateWorkspaceViewModal, DefaultWorkspaceViewQuickActions, @@ -103,30 +104,30 @@ export const GlobalViewsHeader: React.FC = observer(() => { const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; return ( - <> + setCreateViewModal(false)} /> -
-
- {DEFAULT_GLOBAL_VIEWS_LIST.map((tab, index) => ( - - ))} +
+ {DEFAULT_GLOBAL_VIEWS_LIST.map((tab, index) => ( + + ))} - {currentWorkspaceViews?.map((viewId) => )} -
- - {isAuthorizedUser && ( - - )} + {currentWorkspaceViews?.map((viewId) => )}
- + + {isAuthorizedUser ? ( + + ) : ( + <> + )} + ); });