Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,17 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
<div className={"flex flex-col w-full h-full"}>
<div
className={cn(
"px-6 py-2 border-b border-subtle flex items-center gap-4 overflow-hidden w-full justify-between bg-layer-1"
"px-6 py-2 border-b border-subtle flex items-center gap-4 overflow-hidden w-full justify-between bg-surface-1"
)}
>
<Tabs.List background="layer-2" className={"my-2 overflow-x-auto flex w-fit"}>
<Tabs.List className={"overflow-x-auto flex w-fit h-7"}>
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The background prop was removed from Tabs.List, but based on the tabs.tsx implementation, the default background is now 'contained' which applies 'bg-layer-3'. Since this was previously 'layer-2', this change will alter the visual appearance. Verify this is intentional or explicitly set background="contained" for clarity.

Suggested change
<Tabs.List className={"overflow-x-auto flex w-fit h-7"}>
<Tabs.List background="contained" className={"overflow-x-auto flex w-fit h-7"}>

Copilot uses AI. Check for mistakes.
{ANALYTICS_TABS.map((tab) => (
<Tabs.Trigger
key={tab.key}
value={tab.key}
disabled={tab.isDisabled}
size="md"
className="px-3"
className="px-3 h-6"
onClick={() => {
if (!tab.isDisabled) {
handleTabChange(tab.key);
Expand Down
11 changes: 8 additions & 3 deletions apps/web/core/components/analytics/select/project.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { observer } from "mobx-react";
// plane package imports
import { getButtonStyling } from "@plane/propel/button";
import { Logo } from "@plane/propel/emoji-icon-picker";
import { ProjectIcon } from "@plane/propel/icons";
import { ChevronDownIcon, ProjectIcon } from "@plane/propel/icons";
import { CustomSearchSelect } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useProject } from "@/hooks/store/use-project";

Expand Down Expand Up @@ -40,8 +42,9 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
value={value ?? []}
onChange={(val: string[]) => onChange(val)}
options={options}
label={
<div className="flex items-center gap-2 p-1 ">
className="border-none p-0"
customButton={
<div className={cn(getButtonStyling("secondary", "lg"), "gap-2")}>
<ProjectIcon className="h-4 w-4" />
{value && value.length > 3
? `3+ projects`
Expand All @@ -51,8 +54,10 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
.map((p) => getProjectById(p)?.name)
.join(", ")
: "All projects"}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</div>
}
customButtonClassName="border-none p-0 bg-transparent hover:bg-transparent w-auto h-auto"
multiple
/>
);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/core/components/base-layouts/kanban/group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ export const BaseKanbanGroup = observer(function BaseKanbanGroup<T extends IBase
<div
ref={groupRef}
className={cn(
"relative flex flex-shrink-0 flex-col w-[350px] border-[1px] border-transparent p-2 pt-0 max-h-full overflow-y-auto bg-surface-2 rounded-md",
"relative flex flex-shrink-0 flex-col w-[350px] border-[1px] border-transparent p-2 pt-0 max-h-full overflow-y-auto bg-layer-1 rounded-md",
{
"bg-layer-1": isDraggingOver,
},
groupClassName
)}
>
{/* Group Header */}
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-surface-2 px-1 py-2 cursor-pointer">
<div className="sticky top-0 z-[2] w-full flex-shrink-0 px-1 py-2 cursor-pointer">
{renderGroupHeader ? (
renderGroupHeader({ group, itemCount: itemIds.length, isCollapsed, onToggleGroup })
) : (
Expand Down
24 changes: 17 additions & 7 deletions apps/web/core/components/base-layouts/layout-switcher.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { useTranslation } from "@plane/i18n";
import { Tooltip } from "@plane/propel/tooltip";
import type { TBaseLayoutType } from "@plane/types";
import { cn } from "@plane/utils";
import { usePlatformOS } from "@/hooks/use-platform-os";
import { BASE_LAYOUTS } from "./constants";

Expand All @@ -13,6 +14,7 @@ type Props = {
export function LayoutSwitcher(props: Props) {
const { layouts, onChange, selectedLayout } = props;
const { isMobile } = usePlatformOS();
const { t } = useTranslation();

const handleOnChange = (layoutKey: TBaseLayoutType) => {
if (selectedLayout !== layoutKey) {
Expand All @@ -21,21 +23,29 @@ export function LayoutSwitcher(props: Props) {
};

return (
<div className="flex items-center gap-1 rounded-sm bg-layer-1 p-1">
<div className="flex items-center gap-1 rounded-md bg-layer-3 p-1">
{BASE_LAYOUTS.filter((l) => (layouts ? layouts.includes(l.key) : true)).map((layout) => {
const Icon = layout.icon;
return (
<Tooltip key={layout.key} tooltipContent={layout.label} isMobile={isMobile}>
<Tooltip key={layout.key} tooltipContent={t(layout.label)} isMobile={isMobile}>
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 | 🔴 Critical

Critical: Handle missing label for gantt layout.

From the BASE_LAYOUTS constant, the gantt layout doesn't have a label property, which means layout.label is undefined for that layout. Passing undefined to t(key: string) violates the type signature and causes a TypeScript compilation error (similar to the previous i18n_title issue flagged in past reviews).

This is a blocking issue that prevents the build from passing.

🔎 Apply this diff to add a fallback:
-          <Tooltip key={layout.key} tooltipContent={t(layout.label)} isMobile={isMobile}>
+          <Tooltip key={layout.key} tooltipContent={t(layout.label || layout.key)} isMobile={isMobile}>

Alternative solution: Add a label property to the gantt layout in BASE_LAYOUTS constant:

{
  key: "gantt",
  icon: TimelineLayoutIcon,
  label: "Timeline Layout", // Add this
},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Tooltip key={layout.key} tooltipContent={t(layout.label)} isMobile={isMobile}>
<Tooltip key={layout.key} tooltipContent={t(layout.label || layout.key)} isMobile={isMobile}>
🤖 Prompt for AI Agents
In apps/web/core/components/base-layouts/layout-switcher.tsx around line 30,
layout.label can be undefined for the "gantt" layout which causes
t(layout.label) to violate the i18n function signature; update the call to pass
a guaranteed string by using a fallback (e.g., t(layout.label ?? layout.key) or
t(layout.label || 'gantt') ), or alternatively add a label property to the gantt
entry in BASE_LAYOUTS so layout.label is always defined.

<button
type="button"
className={`group grid h-[22px] w-7 place-items-center overflow-hidden rounded-sm transition-all hover:bg-surface-1 ${
selectedLayout === layout.key ? "bg-surface-1 shadow-raised-100" : ""
}`}
className={cn(
"group grid h-5.5 w-7 place-items-center overflow-hidden rounded-sm transition-all hover:bg-layer-transparent-hover",
{
"bg-layer-transparent-active hover:bg-layer-transparent-active": selectedLayout === layout.key,
}
)}
onClick={() => handleOnChange(layout.key)}
>
<Icon
width={14}
height={14}
strokeWidth={2}
className={`h-3.5 w-3.5 ${selectedLayout === layout.key ? "text-primary" : "text-secondary"}`}
className={cn("size-3.5", {
"text-primary": selectedLayout === layout.key,
"text-secondary": selectedLayout !== layout.key,
})}
/>
</button>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,10 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
size="sm"
icon={ChevronRightIcon}
onClick={() => setIsProjectListOpen(!isProjectListOpen)}
className={cn("hidden group-hover/project-item:inline-flex text-placeholder transition-transform", {
className={cn("hidden group-hover/project-item:inline-flex text-placeholder", {
"inline-flex": isMenuActive,
})}
iconClassName={cn("transition-transform", {
"rotate-90": isProjectListOpen,
})}
aria-label={t(
Expand Down
37 changes: 18 additions & 19 deletions apps/web/core/components/workspace/sidebar/projects-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Disclosure, Transition } from "@headlessui/react";
import { EUserPermissions, EUserPermissionsLevel, PROJECT_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { ChevronRightIcon } from "@plane/propel/icons";
import { IconButton } from "@plane/propel/icon-button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { Tooltip } from "@plane/propel/tooltip";
import { Loader } from "@plane/ui";
Expand Down Expand Up @@ -178,39 +179,37 @@ export const SidebarProjectsList = observer(function SidebarProjectsList() {
>
<span className="text-13 font-semibold">{t("projects")}</span>
</Disclosure.Button>
<div className="flex items-center opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto">
<div className="flex items-center gap-1">
{isAuthorizedUser && (
<Tooltip tooltipHeading={t("create_project")} tooltipContent="">
<button
type="button"
data-ph-element={PROJECT_TRACKER_ELEMENTS.SIDEBAR_CREATE_PROJECT_TOOLTIP}
className="p-0.5 rounded-sm hover:bg-layer-1 flex-shrink-0"
<IconButton
variant="ghost"
size="sm"
icon={Plus}
onClick={() => {
setIsProjectModalOpen(true);
}}
data-ph-element={PROJECT_TRACKER_ELEMENTS.SIDEBAR_CREATE_PROJECT_TOOLTIP}
className="hidden group-hover:inline-flex text-placeholder"
aria-label={t("aria_labels.projects_sidebar.create_new_project")}
>
<Plus className="size-3" />
</button>
/>
</Tooltip>
)}
<Disclosure.Button
as="button"
type="button"
className="p-0.5 rounded-sm hover:bg-layer-1 flex-shrink-0"
<IconButton
variant="ghost"
size="sm"
icon={ChevronRightIcon}
onClick={() => toggleListDisclosure(!isAllProjectsListOpen)}
className="text-placeholder"
iconClassName={cn("transition-transform", {
"rotate-90": isAllProjectsListOpen,
})}
aria-label={t(
isAllProjectsListOpen
? "aria_labels.projects_sidebar.close_projects_menu"
: "aria_labels.projects_sidebar.open_projects_menu"
)}
>
<ChevronRightIcon
className={cn("flex-shrink-0 size-3 transition-all", {
"rotate-90": isAllProjectsListOpen,
})}
/>
</Disclosure.Button>
/>
Comment on lines +198 to +212
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The IconButton component is not wrapped with Disclosure.Button. The previous implementation had this button as a Disclosure.Button which is necessary for the Headless UI Disclosure component to function properly. The button needs to be a Disclosure.Button to trigger the disclosure state.

Copilot uses AI. Check for mistakes.
</div>
</div>
<Transition
Expand Down
2 changes: 1 addition & 1 deletion packages/propel/src/charts/area-chart/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const AreaChart = React.memo(function AreaChart<K extends string, T exten
left: margin?.left === undefined ? 20 : margin.left,
}}
>
<CartesianGrid stroke="--alpha(var(--border-color-subtle) / 80%)" vertical={false} />
<CartesianGrid stroke="var(--border-color-subtle)" vertical={false} />
<XAxis
dataKey={xAxis.key}
tick={(props) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/propel/src/charts/bar-chart/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const BarChart = React.memo(function BarChart<K extends string, T extends
barSize={barSize}
className="recharts-wrapper"
>
<CartesianGrid stroke="--alpha(var(--border-color-subtle) / 80%)" vertical={false} />
<CartesianGrid stroke="var(--border-color-subtle)" vertical={false} />
<XAxis
dataKey={xAxis.key}
tick={(props) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/propel/src/charts/line-chart/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const LineChart = React.memo(function LineChart<K extends string, T exten
left: margin?.left === undefined ? 20 : margin.left,
}}
>
<CartesianGrid stroke="--alpha(var(--border-color-subtle) / 80%)" vertical={false} />
<CartesianGrid stroke="var(--border-color-subtle)" vertical={false} />
<XAxis
dataKey={xAxis.key}
tick={(props) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/propel/src/charts/scatter-chart/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const ScatterChart = React.memo(function ScatterChart<K extends string, T
left: margin?.left === undefined ? 20 : margin.left,
}}
>
<CartesianGrid stroke="--alpha(var(--border-color-subtle) / 80%)" vertical={false} />
<CartesianGrid stroke="var(--border-color-subtle)" vertical={false} />
<XAxis
dataKey={xAxis.key}
tick={(props) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/propel/src/charts/tree-map/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const TreeMapChart = React.memo(function TreeMapChart(props: TreeMapChart
data={data}
nameKey="name"
dataKey="value"
stroke="currentColor"
stroke="transparent"
className="bg-layer-1 cursor-pointer"
content={<CustomTreeMapContent />}
animationEasing="ease-out"
Expand Down
1 change: 1 addition & 0 deletions packages/propel/src/icon-button/helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type IconButtonPropsWithChildren = React.ButtonHTMLAttributes<HTMLButtonElement>
VariantProps<typeof iconButtonVariants> & {
icon: React.FC<{ className?: string }>;
loading?: boolean;
iconClassName?: string;
};
export type IconButtonProps = Omit<IconButtonPropsWithChildren, "children">;

Expand Down
14 changes: 9 additions & 5 deletions packages/propel/src/icon-button/icon-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const IconButton = React.forwardRef(function IconButton(
loading = false,
disabled = false,
icon: Icon,
iconClassName = "",
...rest
} = props;

Expand All @@ -27,11 +28,14 @@ const IconButton = React.forwardRef(function IconButton(
{...rest}
>
<Icon
className={cn({
"size-3.5": size === "sm",
"size-4": size === "base" || size === "lg",
"size-5": size === "xl",
})}
className={cn(
{
"size-3.5": size === "sm",
"size-4": size === "base" || size === "lg",
"size-5": size === "xl",
},
iconClassName
)}
/>
</button>
);
Expand Down
60 changes: 36 additions & 24 deletions packages/propel/src/tabs/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@ import * as React from "react";
import { Tabs as TabsPrimitive } from "@base-ui-components/react/tabs";
import { cn } from "../utils/classname";

type BackgroundVariant = "layer-1" | "layer-2" | "layer-3" | "layer-transparent";
type TabsVariant = "contained";

type TabsContextType = {
variant?: TabsVariant;
};

const TabsContext = React.createContext<TabsContextType | undefined>(undefined);
Comment on lines +7 to +11
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The TabsContext is created and provided but never consumed. The variant prop is accepted by TabsTrigger but not used in the component implementation. Either the context should be consumed to use the variant from the root component, or it should be removed if not needed.

Copilot uses AI. Check for mistakes.

type TabsCompound = React.ForwardRefExoticComponent<
React.ComponentProps<typeof TabsPrimitive.Root> & React.RefAttributes<React.ElementRef<typeof TabsPrimitive.Root>>
React.ComponentProps<typeof TabsPrimitive.Root> & {
variant?: TabsVariant;
} & React.RefAttributes<React.ElementRef<typeof TabsPrimitive.Root>>
> & {
List: React.ForwardRefExoticComponent<
React.ComponentProps<typeof TabsPrimitive.List> & {
background?: BackgroundVariant;
background?: TabsVariant;
} & React.RefAttributes<React.ElementRef<typeof TabsPrimitive.List>>
>;
Trigger: React.ForwardRefExoticComponent<
React.ComponentProps<typeof TabsPrimitive.Tab> & { size?: "sm" | "md" | "lg" } & React.RefAttributes<
React.ElementRef<typeof TabsPrimitive.Tab>
>
React.ComponentProps<typeof TabsPrimitive.Tab> & {
size?: "sm" | "md" | "lg";
variant?: TabsVariant;
} & React.RefAttributes<React.ElementRef<typeof TabsPrimitive.Tab>>
>;
Content: React.ForwardRefExoticComponent<
React.ComponentProps<typeof TabsPrimitive.Panel> & React.RefAttributes<React.ElementRef<typeof TabsPrimitive.Panel>>
Expand All @@ -24,39 +33,38 @@ type TabsCompound = React.ForwardRefExoticComponent<
};

const TabsRoot = React.forwardRef(function TabsRoot(
{ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>,
{ className, variant, ...props }: React.ComponentProps<typeof TabsPrimitive.Root> & { variant?: TabsVariant },
ref: React.ForwardedRef<React.ElementRef<typeof TabsPrimitive.Root>>
) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col w-full h-full", className)}
{...props}
ref={ref}
/>
<TabsContext.Provider value={{ variant }}>
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col w-full h-full", className)}
{...props}
ref={ref}
/>
</TabsContext.Provider>
);
});

const TabsList = React.forwardRef(function TabsList(
{
className,
background = "layer-2",
background = "contained",
...props
}: React.ComponentProps<typeof TabsPrimitive.List> & {
background?: BackgroundVariant;
background?: TabsVariant;
},
ref: React.ForwardedRef<React.ElementRef<typeof TabsPrimitive.List>>
) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"flex w-full items-center justify-between gap-1.5 rounded-md text-13 p-0.5 relative overflow-auto",
"flex w-full items-center justify-between gap-1.5 rounded-lg text-13 p-0.5 relative overflow-auto",
{
"bg-layer-1": background === "layer-1",
"bg-layer-2": background === "layer-2",
"bg-layer-3": background === "layer-3",
"bg-layer-transparent": background === "layer-transparent",
"bg-layer-3": background === "contained",
},
className
)}
Expand All @@ -67,16 +75,20 @@ const TabsList = React.forwardRef(function TabsList(
});

const TabsTrigger = React.forwardRef(function TabsTrigger(
{ className, size = "md", ...props }: React.ComponentProps<typeof TabsPrimitive.Tab> & { size?: "sm" | "md" | "lg" },
{
className,
size = "md",
...props
}: React.ComponentProps<typeof TabsPrimitive.Tab> & { size?: "sm" | "md" | "lg"; variant?: TabsVariant },
Comment on lines +78 to +82
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

The variant prop is accepted in the TabsTrigger type signature but is never destructured or used in the component implementation. Either implement the variant functionality or remove it from the type signature.

Copilot uses AI. Check for mistakes.
ref: React.ForwardedRef<React.ElementRef<typeof TabsPrimitive.Tab>>
) {
return (
<TabsPrimitive.Tab
data-slot="tabs-trigger"
className={cn(
"flex items-center justify-center p-1 min-w-fit w-full font-medium text-primary outline-none focus:outline-none cursor-pointer transition-all duration-200 ease-in-out rounded-sm",
"data-[selected]:bg-layer-transparent-active data-[selected]:text-primary data-[selected]:shadow-sm",
"text-placeholder hover:text-tertiary hover:bg-layer-transparent-hover",
"flex items-center justify-center p-1 min-w-fit w-full font-medium text-primary outline-none focus:outline-none cursor-pointer transition-all duration-200 ease-in-out rounded-md border border-transparent",
" data-[selected]:text-primary data-[selected]:shadow-sm data-[selected]:bg-layer-2 data-[selected]:border data-[selected]:border-subtle-1 data-[selected]:raised-200",
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

There are extra spaces at the beginning of this line. Remove the leading space for consistency.

Suggested change
" data-[selected]:text-primary data-[selected]:shadow-sm data-[selected]:bg-layer-2 data-[selected]:border data-[selected]:border-subtle-1 data-[selected]:raised-200",
"data-[selected]:text-primary data-[selected]:shadow-sm data-[selected]:bg-layer-2 data-[selected]:border data-[selected]:border-subtle-1 data-[selected]:raised-200",

Copilot uses AI. Check for mistakes.
"text-placeholder hover:text-tertiary hover:bg-layer-transparent-hover",
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

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

There are extra spaces in this class string. Remove the double space after 'text-placeholder' for consistency.

Suggested change
"text-placeholder hover:text-tertiary hover:bg-layer-transparent-hover",
"text-placeholder hover:text-tertiary hover:bg-layer-transparent-hover",

Copilot uses AI. Check for mistakes.
"disabled:text-placeholder disabled:cursor-not-allowed",
{
"text-11": size === "sm",
Expand Down
Loading