From 85b76b7af81a4bc56a04485158b7de48fbb923b4 Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Wed, 2 Sep 2026 16:08:16 +0530 Subject: [PATCH 1/4] refactor: migrate web Tabs to @makeplane/propel Replace in-repo @plane/propel/tabs with @makeplane/propel/components/tabs in analytics, the image picker popover, and the page navigation pane. Published Tabs omit className on Tabs and TabsPanel, so fill-height layouts use a flex/grid wrapper instead. Tabs.Trigger, Tabs.Content, and Tabs.Indicator become Tab, TabsList, and TabsPanel. --- .../(projects)/analytics/[tabId]/page.tsx | 41 +-- .../components/core/image-picker-popover.tsx | 325 +++++++++--------- .../components/pages/navigation-pane/root.tsx | 16 +- .../pages/navigation-pane/tab-panels/root.tsx | 13 +- .../pages/navigation-pane/tabs-list.tsx | 11 +- 5 files changed, 202 insertions(+), 204 deletions(-) diff --git a/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx b/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx index 3d4735b6574..e1e985e1d4d 100644 --- a/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx +++ b/apps/web/app/(all)/[workspaceSlug]/(projects)/analytics/[tabId]/page.tsx @@ -11,7 +11,7 @@ import { useRouter } from "next/navigation"; import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { EmptyStateDetailed } from "@plane/propel/empty-state"; -import { Tabs } from "@plane/propel/tabs"; +import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs"; // components import { cn } from "@plane/utils"; import AnalyticsFilterActions from "@/components/analytics/analytics-filter-actions"; @@ -73,45 +73,32 @@ function AnalyticsPage({ params }: Route.ComponentProps) { <> {workspaceProjectIds.length > 0 || loader === "init-loader" ? (
- +
- + {ANALYTICS_TABS.map((tab) => ( - { - if (!tab.isDisabled) { - handleTabChange(tab.key); - } - }} - > - {tab.label} - + ))} - +
- {ANALYTICS_TABS.map((tab) => ( - - - - ))} + {/* Grid wrapper: Propel's TabsPanel omits className, so the single mounted panel + gets its fill height from a one-row grid instead. */} +
+ {ANALYTICS_TABS.map((tab) => ( + + + + ))} +
diff --git a/apps/web/core/components/core/image-picker-popover.tsx b/apps/web/core/components/core/image-picker-popover.tsx index be049801d85..442f2dbdb82 100644 --- a/apps/web/core/components/core/image-picker-popover.tsx +++ b/apps/web/core/components/core/image-picker-popover.tsx @@ -14,9 +14,9 @@ import useSWR from "swr"; import { Popover } from "@headlessui/react"; // plane imports import { Input, InputGroup } from "@makeplane/propel/components/input"; +import { Tab, Tabs, TabsList, TabsPanel } from "@makeplane/propel/components/tabs"; import { ACCEPTED_COVER_IMAGE_MIME_TYPES_FOR_REACT_DROPZONE, MAX_FILE_SIZE } from "@plane/constants"; import { useOutsideClickDetector } from "@plane/hooks"; -import { Tabs } from "@plane/propel/tabs"; import { Button, getButtonStyling } from "@plane/propel/button"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; import { EFileAssetType } from "@plane/types"; @@ -206,173 +206,178 @@ function ImagePickerPopoverComponent - - - {enabledTabs.map((tab) => ( - - {tab.title} - - ))} - - -
- - {(unsplashImages || !unsplashError) && ( - <> -
- } - render={({ field: { value, ref } }) => ( - - { - if (e.key === "Enter") { - e.preventDefault(); - setSearchParams(formData.search); - } - }} - value={value} - onChange={(e) => setFormData({ ...formData, search: e.target.value })} - ref={ref} - placeholder="Search for images" - /> - - )} - /> - -
- {unsplashImages ? ( - unsplashImages.length > 0 ? ( -
- {unsplashImages.map((image) => ( -
{ - setIsOpen(false); - onChange(image.urls.regular); - }} - > - {image.alt_description} -
- ))} + {/* Row wrapper: Propel's Tabs root omits className, so it takes its full height by + stretching as a flex item. */} +
+ + + {enabledTabs.map((tab) => ( + + ))} + + {/* Grid wrapper: Propel's TabsPanel omits className, so the single mounted panel + gets its fill height from a one-row grid instead. */} +
+ +
+ {(unsplashImages || !unsplashError) && ( + <> +
+ } + render={({ field: { value, ref } }) => ( + + { + if (e.key === "Enter") { + e.preventDefault(); + setSearchParams(formData.search); + } + }} + value={value} + onChange={(e) => setFormData({ ...formData, search: e.target.value })} + ref={ref} + placeholder="Search for images" + /> + + )} + /> +
- ) : ( -

No images found.

- ) - ) : ( - - - - - - - - - - + {unsplashImages ? ( + unsplashImages.length > 0 ? ( +
+ {unsplashImages.map((image) => ( +
{ + setIsOpen(false); + onChange(image.urls.regular); + }} + > + {image.alt_description} +
+ ))} +
+ ) : ( +

No images found.

+ ) + ) : ( + + + + + + + + + + + )} + )} - - )} - - -
- {Object.values(STATIC_COVER_IMAGES).map((imageUrl, index) => ( -
handleStaticImageSelect(imageUrl)} - > - {`Cover -
- ))} -
-
- -
-
-
- - {image !== null || (value && value !== "") ? ( - <> - image - - ) : ( -
- - {isDragActive ? "Drop image here to upload" : "Drag & drop image here"} - -
- )} + {`Cover +
+ ))} +
+ + +
+
+
+ + {image !== null || (value && value !== "") ? ( + <> + image + + ) : ( +
+ + {isDragActive ? "Drop image here to upload" : "Drag & drop image here"} + +
+ )} - + +
-
- {fileRejections.length > 0 && ( -

- {fileRejections[0].errors[0].code === "file-too-large" - ? "The image size cannot exceed 5 MB." - : "Please upload a file in a valid format."} -

- )} + {fileRejections.length > 0 && ( +

+ {fileRejections[0].errors[0].code === "file-too-large" + ? "The image size cannot exceed 5 MB." + : "Please upload a file in a valid format."} +

+ )} -

File formats supported- .jpeg, .jpg, .png, .webp

+

File formats supported- .jpeg, .jpg, .png, .webp

-
- - +
+ + +
-
-
-
- +
+
+
+
)} diff --git a/apps/web/core/components/pages/navigation-pane/root.tsx b/apps/web/core/components/pages/navigation-pane/root.tsx index de15206e04e..c88cb72f1de 100644 --- a/apps/web/core/components/pages/navigation-pane/root.tsx +++ b/apps/web/core/components/pages/navigation-pane/root.tsx @@ -10,7 +10,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { ArrowRightCircle } from "lucide-react"; // plane imports import { useTranslation } from "@plane/i18n"; -import { Tabs } from "@plane/propel/tabs"; +import { Tabs } from "@makeplane/propel/components/tabs"; import { Tooltip } from "@makeplane/propel/components/tooltip"; // hooks import { useQueryParams } from "@/hooks/use-query-params"; @@ -110,10 +110,16 @@ export const PageNavigationPaneRoot = observer(function PageNavigationPaneRoot(p {ActiveExtension ? ( ) : showNavigationTabs ? ( - - - - + // Row wrapper: Propel's Tabs root omits className, so it takes its full height by + // stretching as a flex item. +
+ +
+ + +
+
+
) : null}
diff --git a/apps/web/core/components/pages/navigation-pane/tab-panels/root.tsx b/apps/web/core/components/pages/navigation-pane/tab-panels/root.tsx index 5cb85bdb63d..2566ad77744 100644 --- a/apps/web/core/components/pages/navigation-pane/tab-panels/root.tsx +++ b/apps/web/core/components/pages/navigation-pane/tab-panels/root.tsx @@ -4,6 +4,8 @@ * See the LICENSE file for details. */ +// plane imports +import { TabsPanel } from "@makeplane/propel/components/tabs"; // components import type { TPageRootHandlers } from "@/components/pages/editor/page-root"; // store @@ -12,7 +14,6 @@ import type { TPageInstance } from "@/store/pages/base-page"; import { PageNavigationPaneAssetsTabPanel } from "./assets"; import { PageNavigationPaneInfoTabPanel } from "./info/root"; import { PageNavigationPaneOutlineTabPanel } from "./outline"; -import { Tabs } from "@plane/propel/tabs"; import { ORDERED_PAGE_NAVIGATION_TABS_LIST } from "."; type Props = { @@ -24,14 +25,16 @@ export function PageNavigationPaneTabPanelsRoot(props: Props) { const { page, versionHistory } = props; return ( - <> + // Grid wrapper: Propel's TabsPanel omits className, so the single mounted panel gets its + // fill height from a one-row grid instead. +
{ORDERED_PAGE_NAVIGATION_TABS_LIST.map((tab) => ( - + {tab.key === "outline" && } {tab.key === "info" && } {tab.key === "assets" && } - + ))} - +
); } diff --git a/apps/web/core/components/pages/navigation-pane/tabs-list.tsx b/apps/web/core/components/pages/navigation-pane/tabs-list.tsx index 6621667aab1..afc7cbf0d1a 100644 --- a/apps/web/core/components/pages/navigation-pane/tabs-list.tsx +++ b/apps/web/core/components/pages/navigation-pane/tabs-list.tsx @@ -5,8 +5,8 @@ */ // plane imports +import { Tab, TabsList } from "@makeplane/propel/components/tabs"; import { useTranslation } from "@plane/i18n"; -import { Tabs } from "@plane/propel/tabs"; // plane web components import { ORDERED_PAGE_NAVIGATION_TABS_LIST } from "@/components/pages/navigation-pane/tab-panels"; @@ -16,14 +16,11 @@ export function PageNavigationPaneTabsList() { return (
- + {ORDERED_PAGE_NAVIGATION_TABS_LIST.map((tab) => ( - - {t(tab.i18n_label)} - + ))} - - +
); } From b3456838b1e4f06146e86e9a263cb38d111dce1e Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Thu, 3 Sep 2026 21:37:16 +0530 Subject: [PATCH 2/4] fix: restore cover picker tab strip to full width Published TabsList is inline-flex, so Images/Upload hugged content instead of spanning the 36rem panel. Stretch the list to the previous full-width segmented bar. --- .../components/core/image-picker-popover.tsx | 325 +++++++++--------- 1 file changed, 164 insertions(+), 161 deletions(-) diff --git a/apps/web/core/components/core/image-picker-popover.tsx b/apps/web/core/components/core/image-picker-popover.tsx index 442f2dbdb82..23bf84a2084 100644 --- a/apps/web/core/components/core/image-picker-popover.tsx +++ b/apps/web/core/components/core/image-picker-popover.tsx @@ -204,177 +204,180 @@ function ImagePickerPopoverComponent
- {/* Row wrapper: Propel's Tabs root omits className, so it takes its full height by - stretching as a flex item. */} -
+ {/* Row wrapper: published Tabs omits className, so fill height comes from stretch. */} +
- - {enabledTabs.map((tab) => ( - - ))} - - {/* Grid wrapper: Propel's TabsPanel omits className, so the single mounted panel - gets its fill height from a one-row grid instead. */} -
- -
- {(unsplashImages || !unsplashError) && ( - <> -
- } - render={({ field: { value, ref } }) => ( - - { - if (e.key === "Enter") { - e.preventDefault(); - setSearchParams(formData.search); - } - }} - value={value} - onChange={(e) => setFormData({ ...formData, search: e.target.value })} - ref={ref} - placeholder="Search for images" - /> - - )} - /> - -
- {unsplashImages ? ( - unsplashImages.length > 0 ? ( -
- {unsplashImages.map((image) => ( -
{ - setIsOpen(false); - onChange(image.urls.regular); - }} - > - {image.alt_description} -
- ))} -
- ) : ( -

No images found.

- ) - ) : ( - - - - - - - - - - - )} - - )} -
-
- -
- {Object.values(STATIC_COVER_IMAGES).map((imageUrl, index) => ( -
handleStaticImageSelect(imageUrl)} - > - {`Cover -
+
+ {/* Published TabsList is inline-flex; stretch it to the previous full-width bar. */} +
+ + {enabledTabs.map((tab) => ( + ))} -
- - -
-
-
- - {image !== null || (value && value !== "") ? ( - <> - image +
+ {/* Grid wrapper: published TabsPanel omits className, so fill height comes from a one-row grid. */} +
+ +
+ {(unsplashImages || !unsplashError) && ( + <> +
+ } + render={({ field: { value, ref } }) => ( + + { + if (e.key === "Enter") { + e.preventDefault(); + setSearchParams(formData.search); + } + }} + value={value} + onChange={(e) => setFormData({ ...formData, search: e.target.value })} + ref={ref} + placeholder="Search for images" + /> + + )} /> - - ) : ( -
- - {isDragActive ? "Drop image here to upload" : "Drag & drop image here"} - +
- )} + {unsplashImages ? ( + unsplashImages.length > 0 ? ( +
+ {unsplashImages.map((image) => ( +
{ + setIsOpen(false); + onChange(image.urls.regular); + }} + > + {image.alt_description} +
+ ))} +
+ ) : ( +

No images found.

+ ) + ) : ( + + + + + + + + + + + )} + + )} +
+ + +
+ {Object.values(STATIC_COVER_IMAGES).map((imageUrl, index) => ( +
handleStaticImageSelect(imageUrl)} + > + {`Cover +
+ ))} +
+
+ +
+
+
+ + {image !== null || (value && value !== "") ? ( + <> + image + + ) : ( +
+ + {isDragActive ? "Drop image here to upload" : "Drag & drop image here"} + +
+ )} - + +
-
- {fileRejections.length > 0 && ( -

- {fileRejections[0].errors[0].code === "file-too-large" - ? "The image size cannot exceed 5 MB." - : "Please upload a file in a valid format."} -

- )} + {fileRejections.length > 0 && ( +

+ {fileRejections[0].errors[0].code === "file-too-large" + ? "The image size cannot exceed 5 MB." + : "Please upload a file in a valid format."} +

+ )} -

File formats supported- .jpeg, .jpg, .png, .webp

+

File formats supported- .jpeg, .jpg, .png, .webp

-
- - +
+ + +
-
-
+ +
From dc818f94df1fdbbe6a16d82814f2378492f9febf Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Fri, 4 Sep 2026 14:42:43 +0530 Subject: [PATCH 3/4] chore: sync apps/space lockfile to @makeplane/propel 0.3.0 preview's lockfile still resolved apps/space to 0.2.0 after the catalog bump, so pnpm install --frozen-lockfile failed in CI. --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7e8251a913..c1091fa51f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -876,7 +876,7 @@ importers: version: 2.2.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@makeplane/propel': specifier: 'catalog:' - version: 0.2.0(@date-fns/tz@1.4.1)(@types/react@19.2.17)(date-fns@4.1.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.17) + version: 0.3.0(@date-fns/tz@1.4.1)(@types/react@19.2.17)(date-fns@4.1.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwindcss@4.1.17) '@plane/constants': specifier: workspace:* version: link:../../packages/constants From 6a38befc09a4349080aee3bb849aa87dbc32fba7 Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Fri, 4 Sep 2026 14:45:43 +0530 Subject: [PATCH 4/4] fix: restore cover picker tab panel mt-3 p-3 spacing Match the previous scroll wrapper inset so thumbnail grid sizing stays aligned with the 36rem picker layout. --- apps/web/core/components/core/image-picker-popover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/core/components/core/image-picker-popover.tsx b/apps/web/core/components/core/image-picker-popover.tsx index 23bf84a2084..7b235890cc6 100644 --- a/apps/web/core/components/core/image-picker-popover.tsx +++ b/apps/web/core/components/core/image-picker-popover.tsx @@ -219,7 +219,7 @@ function ImagePickerPopoverComponent
{/* Grid wrapper: published TabsPanel omits className, so fill height comes from a one-row grid. */} -
+
{(unsplashImages || !unsplashError) && (