Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions apps/web/core/components/analytics/work-items/modal/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,11 @@ export const WorkItemsModalMainContent = observer(function WorkItemsModalMainCon
);

return (
<Tab.Group as={React.Fragment}>
<div className="flex flex-col gap-14 overflow-y-auto p-6">
<TotalInsights analyticsType="work-items" peekView={!fullScreen} />
<CreatedVsResolved />
<CustomizedInsights peekView={!fullScreen} isEpic={isEpic} />
<WorkItemsInsightTable />
</div>
</Tab.Group>
<div className="flex flex-col gap-14 overflow-y-auto p-6">
<TotalInsights analyticsType="work-items" peekView={!fullScreen} />
<CreatedVsResolved />
<CustomizedInsights peekView={!fullScreen} isEpic={isEpic} />
<WorkItemsInsightTable />
</div>
);
});
167 changes: 83 additions & 84 deletions apps/web/core/components/core/image-picker-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { useDropzone } from "react-dropzone";
import type { Control } from "react-hook-form";
import { Controller } from "react-hook-form";
import useSWR from "swr";
import { Tab, Popover } from "@headlessui/react";
import { Popover } from "@headlessui/react";
// plane imports
import { ACCEPTED_COVER_IMAGE_MIME_TYPES_FOR_REACT_DROPZONE, MAX_FILE_SIZE } from "@plane/constants";
import { useOutsideClickDetector } from "@plane/hooks";
import { Button } from "@plane/propel/button";
import { Tabs } from "@plane/propel/tabs";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { EFileAssetType } from "@plane/types";
import { Input, Loader } from "@plane/ui";
Expand Down Expand Up @@ -197,91 +198,88 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr
ref={imagePickerRef}
className="flex h-96 w-80 flex-col overflow-auto rounded border border-custom-border-300 bg-custom-background-100 p-3 shadow-2xl md:h-[28rem] md:w-[36rem]"
>
<Tab.Group>
<Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1">
<Tabs defaultValue={tabOptions[0].key} className={"h-full overflow-hidden"}>
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary curly braces: The className value is a static string and doesn't need to be wrapped in curly braces. Use className="h-full overflow-hidden" instead of className={"h-full overflow-hidden"}.

Suggested change
<Tabs defaultValue={tabOptions[0].key} className={"h-full overflow-hidden"}>
<Tabs defaultValue={tabOptions[0].key} className="h-full overflow-hidden">

Copilot uses AI. Check for mistakes.
<Tabs.List className="p-1 w-fit">
{tabOptions.map((tab) => (
<Tab
<Tabs.Trigger
key={tab.key}
className={({ selected }) =>
`rounded px-4 py-1 text-center text-sm outline-none transition-colors ${
selected ? "bg-custom-primary text-white" : "text-custom-text-100"
}`
}
value={tab.key}
className="rounded px-4 py-1 text-center text-sm outline-none transition-colors data-[selected]:bg-custom-primary data-[selected]:text-white text-custom-text-100"
>
{tab.title}
</Tab>
</Tabs.Trigger>
))}
</Tab.List>
<Tab.Panels className="vertical-scrollbar scrollbar-md h-full w-full flex-1 overflow-y-auto overflow-x-hidden">
<Tab.Panel className="mt-4 h-full w-full space-y-4">
{(unsplashImages || !unsplashError) && (
<>
<div className="flex gap-x-2">
<Controller
control={control}
name="search"
render={({ field: { value, ref } }) => (
<Input
id="search"
name="search"
type="text"
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
setSearchParams(formData.search);
}
</Tabs.List>
Comment on lines +201 to +212
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n apps/web/core/components/core/image-picker-popover.tsx | head -100

Repository: makeplane/plane

Length of output: 3873


🏁 Script executed:

sed -n '190,220p' apps/web/core/components/core/image-picker-popover.tsx

Repository: makeplane/plane

Length of output: 1486


🏁 Script executed:

sed -n '220,280p' apps/web/core/components/core/image-picker-popover.tsx

Repository: makeplane/plane

Length of output: 3067


🏁 Script executed:

sed -n '275,350p' apps/web/core/components/core/image-picker-popover.tsx

Repository: makeplane/plane

Length of output: 3911


🏁 Script executed:

sed -n '360,380p' apps/web/core/components/core/image-picker-popover.tsx

Repository: makeplane/plane

Length of output: 485


Filter disabled tabs and set default to first enabled tab.

The tabOptions array includes an isEnabled field (set on line 68 based on hasUnsplashConfigured), but this is not being used when rendering tabs. All tabs render regardless of enabled state (lines 203-212), and defaultValue always uses tabOptions[0].key ("unsplash" at line 201), which may be disabled.

If hasUnsplashConfigured is false, the Unsplash tab button displays even though it shouldn't be available, and the component attempts to set a disabled tab as the default.

Filter tabs by isEnabled and calculate the default value from the first enabled tab:

-          <Tabs defaultValue={tabOptions[0].key} className={"h-full overflow-hidden"}>
+          <Tabs defaultValue={tabOptions.find(tab => tab.isEnabled)?.key ?? "images"} className={"h-full overflow-hidden"}>
             <Tabs.List className="p-1 w-fit">
-              {tabOptions.map((tab) => (
+              {tabOptions.filter(tab => tab.isEnabled).map((tab) => (
🤖 Prompt for AI Agents
In apps/web/core/components/core/image-picker-popover.tsx around lines 201 to
212, the Tabs are rendered from tabOptions regardless of each tab's isEnabled
flag and defaultValue uses tabOptions[0].key which may be disabled; filter
tabOptions to only include tabs where isEnabled is true when computing the list
to render and when computing the default tab key (use the first enabled tab's
key), and handle the case where no tabs are enabled (e.g., fall back to
undefined or a safe default) so the Tabs component never tries to select a
disabled entry.


<div className="mt-4 flex-1 overflow-auto">
{(unsplashImages || !unsplashError) && (
<Tabs.Content className="h-full w-full space-y-4" value="unsplash">
<div className="flex gap-x-2">
<Controller
control={control}
name="search"
render={({ field: { value, ref } }) => (
<Input
id="search"
name="search"
type="text"
onKeyDown={(e) => {
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"
className="w-full text-sm"
/>
)}
/>
<Button variant="primary" onClick={() => setSearchParams(formData.search)} size="sm">
Search
</Button>
</div>
{unsplashImages ? (
unsplashImages.length > 0 ? (
<div className="grid grid-cols-4 gap-4">
{unsplashImages.map((image) => (
<div
key={image.id}
className="relative col-span-2 aspect-video md:col-span-1"
onClick={() => {
setIsOpen(false);
onChange(image.urls.regular);
}}
value={value}
onChange={(e) => setFormData({ ...formData, search: e.target.value })}
ref={ref}
placeholder="Search for images"
className="w-full text-sm"
/>
)}
/>
<Button variant="primary" onClick={() => setSearchParams(formData.search)} size="sm">
Search
</Button>
</div>
{unsplashImages ? (
unsplashImages.length > 0 ? (
<div className="grid grid-cols-4 gap-4">
{unsplashImages.map((image) => (
<div
key={image.id}
className="relative col-span-2 aspect-video md:col-span-1"
onClick={() => {
setIsOpen(false);
onChange(image.urls.regular);
}}
>
<img
src={image.urls.small}
alt={image.alt_description}
className="absolute left-0 top-0 h-full w-full cursor-pointer rounded object-cover"
/>
</div>
))}
</div>
) : (
<p className="pt-7 text-center text-xs text-custom-text-300">No images found.</p>
)
>
<img
src={image.urls.small}
alt={image.alt_description}
className="absolute left-0 top-0 h-full w-full cursor-pointer rounded object-cover"
/>
</div>
))}
</div>
) : (
<Loader className="grid grid-cols-4 gap-4">
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
</Loader>
)}
</>
)}
</Tab.Panel>
<Tab.Panel className="mt-4 h-full w-full space-y-4">
<p className="pt-7 text-center text-xs text-custom-text-300">No images found.</p>
)
) : (
<Loader className="grid grid-cols-4 gap-4">
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
<Loader.Item height="80px" width="100%" />
</Loader>
)}
</Tabs.Content>
)}

<Tabs.Content className="h-full w-full space-y-4" value="images">
<div className="grid grid-cols-4 gap-4">
{Object.values(STATIC_COVER_IMAGES).map((imageUrl, index) => (
<div
Expand All @@ -297,8 +295,9 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr
</div>
))}
</div>
</Tab.Panel>
<Tab.Panel className="mt-4 h-full w-full">
</Tabs.Content>

<Tabs.Content className="h-full w-full" value="upload">
<div className="flex h-full w-full flex-col gap-y-2">
<div className="flex w-full flex-1 items-center gap-3">
<div
Expand Down Expand Up @@ -365,9 +364,9 @@ export const ImagePickerPopover = observer(function ImagePickerPopover(props: Pr
</Button>
</div>
</div>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</Tabs.Content>
</div>
</Tabs>
</div>
</Popover.Panel>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { FC } from "react";
import { observer } from "mobx-react";
import { Tab } from "@headlessui/react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { Tabs } from "@plane/propel/tabs";
import type { TWorkItemFilterCondition } from "@plane/shared-state";
import type { TCycleDistribution, TCycleEstimateDistribution, TCyclePlotType } from "@plane/types";
import { cn, toFilterArray } from "@plane/utils";
Expand Down Expand Up @@ -54,8 +54,6 @@ export const CycleProgressStats = observer(function CycleProgressStats(props: TC
`cycle-analytics-tab-${cycleId}`,
"stat-assignees"
);
// derived values
const currentTabIndex = (tab: string): number => PROGRESS_STATS.findIndex((stat) => stat.key === tab);
const currentDistribution = distribution as TCycleDistribution;
const currentEstimateDistribution = distribution as TCycleEstimateDistribution;
const selectedAssigneeIds = toFilterArray(selectedFilters?.assignees?.value || []) as string[];
Expand Down Expand Up @@ -116,9 +114,8 @@ export const CycleProgressStats = observer(function CycleProgressStats(props: TC

return (
<div>
<Tab.Group defaultIndex={currentTabIndex(currentTab ? currentTab : "stat-assignees")}>
<Tab.List
as="div"
<Tabs defaultValue={currentTab ?? "stat-assignees"} onValueChange={(value) => setCycleTab(value)}>
<Tabs.List
className={cn(
`flex w-full items-center justify-between gap-2 rounded-md p-1`,
roundedTab ? `rounded-3xl` : `rounded-md`,
Expand All @@ -127,49 +124,48 @@ export const CycleProgressStats = observer(function CycleProgressStats(props: TC
)}
>
{PROGRESS_STATS.map((stat) => (
<Tab
<Tabs.Trigger
key={stat.key}
value={stat.key}
className={cn(
`p-1 w-full text-custom-text-100 outline-none focus:outline-none cursor-pointer transition-all`,
roundedTab ? `rounded-3xl border border-custom-border-200` : `rounded`,
stat.key === currentTab
? "bg-custom-background-100 text-custom-text-300"
: "text-custom-text-400 hover:text-custom-text-300"
"data-[selected]:bg-custom-background-100 data-[selected]:text-custom-text-300",
"text-custom-text-400 hover:text-custom-text-300"
)}
key={stat.key}
onClick={() => setCycleTab(stat.key)}
>
{t(stat.i18n_title)}
</Tab>
</Tabs.Trigger>
))}
</Tab.List>
<Tab.Panels className="py-3 text-custom-text-200">
<Tab.Panel key={"stat-states"}>
</Tabs.List>
<div className="py-3 text-custom-text-200">
<Tabs.Content value="stat-states">
<StateGroupStatComponent
distribution={distributionStateData}
handleStateGroupFiltersUpdate={handleStateGroupFiltersUpdate}
isEditable={isEditable}
selectedStateGroups={selectedStateGroups}
totalIssuesCount={totalIssuesCount}
/>
</Tab.Panel>
<Tab.Panel key={"stat-assignees"}>
</Tabs.Content>
<Tabs.Content value="stat-assignees">
<AssigneeStatComponent
distribution={distributionAssigneeData}
handleAssigneeFiltersUpdate={handleAssigneeFiltersUpdate}
isEditable={isEditable}
selectedAssigneeIds={selectedAssigneeIds}
/>
</Tab.Panel>
<Tab.Panel key={"stat-labels"}>
</Tabs.Content>
<Tabs.Content value="stat-labels">
<LabelStatComponent
distribution={distributionLabelData}
handleLabelFiltersUpdate={handleLabelFiltersUpdate}
isEditable={isEditable}
selectedLabelIds={selectedLabelIds}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</Tabs.Content>
</div>
</Tabs>
</div>
);
});
41 changes: 18 additions & 23 deletions apps/web/core/components/license/modal/card/base-paid-plan-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { FC } from "react";
import { useState } from "react";
import { observer } from "mobx-react";
import { CheckCircle } from "lucide-react";
import { Tab } from "@headlessui/react";
// plane imports
import { Tabs } from "@plane/propel/tabs";
// helpers
import type { EProductSubscriptionEnum, TBillingFrequency, TSubscriptionPrice } from "@plane/types";
import { getSubscriptionBackgroundColor, getUpgradeCardVariantStyle } from "@plane/ui";
Expand Down Expand Up @@ -39,32 +39,27 @@ export const BasePaidPlanCard = observer(function BasePaidPlanCard(props: TBaseP

return (
<div className={cn("flex flex-col py-6 px-3", upgradeCardVariantStyle)}>
<Tab.Group selectedIndex={selectedPlan === "month" ? 0 : 1}>
<div className="flex w-full justify-center h-9">
<Tab.List
className={cn("flex space-x-1 rounded-md p-0.5 w-60", getSubscriptionBackgroundColor(planVariant, "50"))}
>
<Tabs value={selectedPlan} onValueChange={(value) => setSelectedPlan(value as TBillingFrequency)}>
<div className="flex w-full justify-center">
<Tabs.List className={cn("flex rounded-md w-60", getSubscriptionBackgroundColor(planVariant, "50"))}>
{prices.map((price: TSubscriptionPrice) => (
<Tab
<Tabs.Trigger
key={price.key}
className={({ selected }) =>
cn(
"w-full rounded py-1 text-sm font-medium leading-5",
selected
? "bg-custom-background-100 text-custom-text-100 shadow"
: "text-custom-text-300 hover:text-custom-text-200"
)
}
onClick={() => setSelectedPlan(price.recurring)}
value={price.recurring}
className={cn(
"w-full rounded text-sm font-medium leading-5 py-2",
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra whitespace detected: There are two spaces between "rounded" and "text-sm". This should be a single space for consistency.

Suggested change
"w-full rounded text-sm font-medium leading-5 py-2",
"w-full rounded text-sm font-medium leading-5 py-2",

Copilot uses AI. Check for mistakes.
"data-[selected]:bg-custom-background-100 data-[selected]:text-custom-text-100 data-[selected]:shadow",
"text-custom-text-300 hover:text-custom-text-200"
)}
>
{renderPriceContent(price)}
</Tab>
</Tabs.Trigger>
))}
</Tab.List>
</Tabs.List>
</div>
<Tab.Panels>
<div>
{prices.map((price: TSubscriptionPrice) => (
<Tab.Panel key={price.key}>
<Tabs.Content key={price.key} value={price.recurring}>
<div className="pt-6 text-center">
<div className="text-xl font-medium">Plane {planeName}</div>
{renderActionButton(price)}
Expand All @@ -88,10 +83,10 @@ export const BasePaidPlanCard = observer(function BasePaidPlanCard(props: TBaseP
</ul>
{extraFeatures && <div>{extraFeatures}</div>}
</div>
</Tab.Panel>
</Tabs.Content>
))}
</Tab.Panels>
</Tab.Group>
</div>
</Tabs>
</div>
);
});
Loading
Loading