Skip to content
Open
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { observer } from "mobx-react";
import { CalendarCheck2 } from "lucide-react";
import { DueDatePropertyIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { cn } from "@plane/utils";
// helpers
Expand Down Expand Up @@ -33,7 +33,7 @@ export const IssueBlockDate = observer((props: Props) => {
"border-[0.5px] border-custom-border-300": shouldShowBorder,
})}
>
<CalendarCheck2 className="size-3 flex-shrink-0" />
<DueDatePropertyIcon className="size-3 flex-shrink-0" />
{formattedDate ? formattedDate : "No Date"}
</div>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { observer } from "mobx-react";
import { Tags } from "lucide-react";
import { LabelPropertyIcon } from "@plane/propel/icons";
// plane imports
import { Tooltip } from "@plane/propel/tooltip";
// hooks
Expand All @@ -25,7 +25,7 @@ export const IssueBlockLabels = observer(({ labelIds, shouldShowLabel = false }:
<div
className={`flex h-full items-center justify-center gap-2 rounded px-2.5 py-1 text-xs border-[0.5px] border-custom-border-300`}
>
<Tags className="h-3.5 w-3.5" strokeWidth={2} />
<LabelPropertyIcon className="h-3.5 w-3.5" strokeWidth={2} />
{shouldShowLabel && <span>No Labels</span>}
</div>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { observer } from "mobx-react";
// icons
import type { LucideIcon } from "lucide-react";
import { Users } from "lucide-react";
import { MembersPropertyIcon } from "@plane/propel/icons";
// plane ui
import { Avatar, AvatarGroup } from "@plane/ui";
// plane utils
Expand Down Expand Up @@ -49,7 +49,11 @@ export const ButtonAvatars: React.FC<AvatarProps> = observer((props: AvatarProps
}
}

return Icon ? <Icon className="h-3 w-3 flex-shrink-0" /> : <Users className="h-3 w-3 mx-[4px] flex-shrink-0" />;
return Icon ? (
<Icon className="h-3 w-3 flex-shrink-0" />
) : (
<MembersPropertyIcon className="h-3 w-3 mx-[4px] flex-shrink-0" />
);
});

export const IssueBlockMembers = observer(({ memberIds, shouldShowBorder = true }: Props) => {
Expand Down
19 changes: 14 additions & 5 deletions apps/space/core/components/issues/navbar/layout-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import type { LucideProps } from "lucide-react";
import { List, Kanban } from "lucide-react";
import type { TIssueLayout } from "@plane/constants";
import { ListLayoutIcon, BoardLayoutIcon } from "@plane/propel/icons";
import type { ISvgIcons } from "@plane/propel/icons";

export const IssueLayoutIcon = ({
layout,
size,
...props
}: { layout: TIssueLayout; size?: number } & Omit<ISvgIcons, "width" | "height">) => {
const iconProps = {
...props,
...(size && { width: size, height: size }),
};

export const IssueLayoutIcon = ({ layout, ...props }: { layout: TIssueLayout } & LucideProps) => {
switch (layout) {
case "list":
return <List {...props} />;
return <ListLayoutIcon {...iconProps} />;
case "kanban":
return <Kanban {...props} />;
return <BoardLayoutIcon {...iconProps} />;
default:
return null;
}
Comment on lines 15 to 22
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

🧩 Analysis chain

Verify incomplete layout case handling.

The switch statement only handles "list" and "kanban" cases, but TIssueLayout includes "calendar", "spreadsheet", and "gantt". The web version (apps/web/core/components/issues/issue-layouts/layout-icon.tsx) handles all five layout types.

If the space app intentionally doesn't support calendar/spreadsheet/gantt layouts, this is fine. Otherwise, users selecting those layouts would see no icon (null return).


🏁 Script executed:

#!/bin/bash
# Verify if space app uses calendar, spreadsheet, or gantt layouts

echo "=== Searching for calendar/spreadsheet/gantt layout references in space app ==="
rg -n --type=ts --type=tsx -C3 'layout.*=.*(calendar|spreadsheet|gantt)' apps/space/

echo -e "\n=== Checking TIssueLayout usage in space app ==="
rg -n --type=ts --type=tsx 'TIssueLayout' apps/space/ -A2 -B2

Length of output: 185



🏁 Script executed:

#!/bin/bash
# Fix file type recognition by using glob patterns instead

echo "=== Searching for calendar/spreadsheet/gantt layout references in space app ==="
rg -n -g '*.ts' -g '*.tsx' -C3 'layout.*=.*(calendar|spreadsheet|gantt)' apps/space/

echo -e "\n=== Checking TIssueLayout type definition in space app ==="
rg -n -g '*.ts' -g '*.tsx' 'type TIssueLayout|enum.*Layout' apps/space/ -A5

echo -e "\n=== Checking if layout-icon.tsx exists and its current content ==="
find apps/space -name 'layout-icon.tsx' -exec head -30 {} \;

echo -e "\n=== Searching for where layout prop values are defined/used in space ==="
rg -n -g '*.ts' -g '*.tsx' '"kanban"|"list"|"calendar"|"spreadsheet"|"gantt"' apps/space/ | head -20

Length of output: 2831



🏁 Script executed:

#!/bin/bash
# Check the web version layout-icon to see all icon implementations

echo "=== Checking web version layout-icon.tsx ==="
find apps/web -name 'layout-icon.tsx' -exec cat {} \;

echo -e "\n=== Checking available icon imports in space app ==="
rg -n -g '*.ts' -g '*.tsx' 'CalendarLayoutIcon|SpreadsheetLayoutIcon|GanttLayoutIcon' apps/space/

echo -e "\n=== Checking available icons from propel/icons ==="
rg -n -g '*.ts' -g '*.tsx' 'export.*LayoutIcon' apps/ | grep -i 'calendar\|spreadsheet\|gantt\|propel'

Length of output: 1127


Add missing layout icons for calendar, spreadsheet, and gantt.

The space app's TIssueLayout type includes all five layout types, and controls.tsx actively enables users to select calendar, spreadsheet, and gantt layouts. However, layout-icon.tsx only handles list and kanban, returning null for the other three—leaving users without icons when selecting unsupported layouts.

The web version implements this correctly with all five icons. Import CalendarLayoutIcon, SheetLayoutIcon, and TimelineLayoutIcon from @plane/propel/icons and add the corresponding switch cases:

case "calendar":
  return <CalendarLayoutIcon {...iconProps} />;
case "spreadsheet":
  return <SheetLayoutIcon {...iconProps} />;
case "gantt":
  return <TimelineLayoutIcon {...iconProps} />;
🤖 Prompt for AI Agents
In apps/space/core/components/issues/navbar/layout-icon.tsx around lines 15 to
22, the switch only returns icons for "list" and "kanban" and returns null for
other layout types; import CalendarLayoutIcon, SheetLayoutIcon, and
TimelineLayoutIcon from @plane/propel/icons and add switch cases for "calendar"
-> CalendarLayoutIcon, "spreadsheet" -> SheetLayoutIcon, and "gantt" ->
TimelineLayoutIcon passing the existing iconProps so all TIssueLayout values
render the proper icons.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { CalendarCheck2, Signal } from "lucide-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { DoubleCircleIcon, StateGroupIcon } from "@plane/propel/icons";
import { StatePropertyIcon, StateGroupIcon, PriorityPropertyIcon, DueDatePropertyIcon } from "@plane/propel/icons";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { cn, getIssuePriorityFilters } from "@plane/utils";
// components
Expand Down Expand Up @@ -66,7 +65,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet
<div className={`space-y-2 ${mode === "full" ? "pt-3" : ""}`}>
<div className="flex items-center gap-3 h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<DoubleCircleIcon className="size-4 flex-shrink-0" />
<StatePropertyIcon className="size-4 flex-shrink-0" />
<span>State</span>
</div>
<div className="w-3/4 flex items-center gap-1.5 py-0.5 text-sm">
Expand All @@ -77,7 +76,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet

<div className="flex items-center gap-3 h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<Signal className="size-4 flex-shrink-0" />
<PriorityPropertyIcon className="size-4 flex-shrink-0" />
<span>Priority</span>
</div>
<div className="w-3/4">
Expand Down Expand Up @@ -106,7 +105,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet

<div className="flex items-center gap-3 h-8">
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
<CalendarCheck2 className="size-4 flex-shrink-0" />
<DueDatePropertyIcon className="size-4 flex-shrink-0" />
<span>Due date</span>
</div>
<div>
Expand All @@ -116,7 +115,7 @@ export const PeekOverviewIssueProperties: React.FC<Props> = observer(({ issueDet
"text-red-500": shouldHighlightIssueDueDate(issueDetails.target_date, state?.group),
})}
>
<CalendarCheck2 className="size-3" />
<DueDatePropertyIcon className="size-3" />
{renderFormattedDate(issueDetails.target_date)}
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import { useCallback, useState } from "react";
import { useParams } from "next/navigation";
// icons
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
import { ChevronDown } from "lucide-react";
// plane imports
import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { CalendarLayoutIcon, BoardLayoutIcon, ListLayoutIcon } from "@plane/propel/icons";
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types";
import { EIssuesStoreType } from "@plane/types";
import { CustomMenu } from "@plane/ui";
Expand All @@ -20,9 +21,9 @@ import { useIssues } from "@/hooks/store/use-issues";
import { useProject } from "@/hooks/store/use-project";

const SUPPORTED_LAYOUTS = [
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: List },
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: Kanban },
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: Calendar },
{ key: "list", titleTranslationKey: "issue.layouts.list", icon: ListLayoutIcon },
{ key: "kanban", titleTranslationKey: "issue.layouts.kanban", icon: BoardLayoutIcon },
{ key: "calendar", titleTranslationKey: "issue.layouts.calendar", icon: CalendarLayoutIcon },
];

export const CycleIssuesMobileHeader = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";

import type React from "react";
import { observer } from "mobx-react";
// ui
import { GanttChartSquare, LayoutGrid, List } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { TimelineLayoutIcon, GridLayoutIcon, ListLayoutIcon } from "@plane/propel/icons";
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

🧩 Analysis chain

Icon migration looks good, but verify type compatibility.

The icon replacements are correctly implemented. However, the icon type definition at line 16 specifies React.FC<React.SVGProps<SVGSVGElement>>, but the actual icon components are typed as React.FC<ISvgIcons>. The ISvgIcons interface likely has more specific props (e.g., color with "currentColor" default) than the generic SVGProps.

Verify that the type definition is compatible with the actual icon components:

Also applies to: 21-21, 26-26, 31-31, 48-48


🏁 Script executed:

#!/bin/bash
# Check the ISvgIcons interface definition to confirm type compatibility
rg -nP "interface ISvgIcons|type ISvgIcons" --type=ts packages/propel/src/icons/

Length of output: 104



---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full ISvgIcons interface definition
cat packages/propel/src/icons/type.ts

Length of output: 127



---

🏁 Script executed:

```shell
#!/bin/bash
# View the mobile-header.tsx file to see the actual CYCLE_VIEW_LAYOUTS type definition
cat "apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/mobile-header.tsx"

Length of output: 2302


Fix type definition for icon components to match actual types.

The icon type definition at line 16 is incompatible with the actual icon components. The type specifies React.FC<React.SVGProps<SVGSVGElement>>, but the imported icons (ListLayoutIcon, GridLayoutIcon, TimelineLayoutIcon) are typed as React.FC<ISvgIcons>. Since ISvgIcons extends React.SVGAttributes<SVGElement> and adds a percentage?: number property, the type mismatch creates a type safety violation.

Update line 16:

icon: React.FC<ISvgIcons>;
🤖 Prompt for AI Agents
In
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(list)/mobile-header.tsx
around lines 6 to 16, the icon prop type is declared as
React.FC<React.SVGProps<SVGSVGElement>> which mismatches the actual imported
icons typed as React.FC<ISvgIcons>; update the icon type to React.FC<ISvgIcons>
and ensure ISvgIcons is imported from the icon package (or declared/aliased) so
the components accept the extra percentage?: number property and align types
with the imported ListLayoutIcon, GridLayoutIcon and TimelineLayoutIcon.

// plane package imports
import type { TCycleLayoutOptions } from "@plane/types";
import { CustomMenu } from "@plane/ui";
Expand All @@ -13,22 +13,22 @@ import { useProject } from "@/hooks/store/use-project";

const CYCLE_VIEW_LAYOUTS: {
key: TCycleLayoutOptions;
icon: LucideIcon;
icon: React.FC<React.SVGProps<SVGSVGElement>>;
title: string;
}[] = [
{
key: "list",
icon: List,
icon: ListLayoutIcon,
title: "List layout",
},
{
key: "board",
icon: LayoutGrid,
icon: GridLayoutIcon,
title: "Gallery layout",
},
{
key: "gantt",
icon: GanttChartSquare,
icon: TimelineLayoutIcon,
title: "Timeline layout",
},
];
Expand All @@ -45,7 +45,7 @@ export const CyclesListMobileHeader = observer(() => {
// placement="bottom-start"
customButton={
<span className="flex items-center gap-2">
<List className="h-4 w-4" />
<ListLayoutIcon className="h-4 w-4" />
<span className="flex flex-grow justify-center text-custom-text-200 text-sm">Layout</span>
</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { useCallback, useState } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// icons
import { Calendar, ChevronDown, Kanban, List } from "lucide-react";
import { ChevronDown } from "lucide-react";
// plane imports
import { EIssueFilterType, ISSUE_LAYOUTS, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { CalendarLayoutIcon, BoardLayoutIcon, ListLayoutIcon } from "@plane/propel/icons";
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, EIssueLayoutTypes } from "@plane/types";
import { EIssuesStoreType } from "@plane/types";
import { CustomMenu } from "@plane/ui";
Expand All @@ -21,9 +22,9 @@ import { useModule } from "@/hooks/store/use-module";
import { useProject } from "@/hooks/store/use-project";

const SUPPORTED_LAYOUTS = [
{ key: "list", i18n_title: "issue.layouts.list", icon: List },
{ key: "kanban", i18n_title: "issue.layouts.kanban", icon: Kanban },
{ key: "calendar", i18n_title: "issue.layouts.calendar", icon: Calendar },
{ key: "list", i18n_title: "issue.layouts.list", icon: ListLayoutIcon },
{ key: "kanban", i18n_title: "issue.layouts.kanban", icon: BoardLayoutIcon },
{ key: "calendar", i18n_title: "issue.layouts.calendar", icon: CalendarLayoutIcon },
];

export const ModuleIssuesMobileHeader = observer(() => {
Expand Down
39 changes: 19 additions & 20 deletions apps/web/ce/components/issues/issue-layouts/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import type { FC } from "react";
import {
CalendarCheck2,
CalendarClock,
CalendarDays,
LayersIcon,
Link2,
Paperclip,
Signal,
Tag,
Triangle,
Users,
} from "lucide-react";
import { CalendarDays, LayersIcon, Link2, Paperclip } from "lucide-react";
// types
import type { ISvgIcons } from "@plane/propel/icons";
import { CycleIcon, DoubleCircleIcon, ModuleIcon } from "@plane/propel/icons";
import {
CycleIcon,
StatePropertyIcon,
ModuleIcon,
MembersPropertyIcon,
DueDatePropertyIcon,
EstimatePropertyIcon,
LabelPropertyIcon,
PriorityPropertyIcon,
StartDatePropertyIcon,
} from "@plane/propel/icons";
import type { IGroupByColumn, IIssueDisplayProperties, TGetColumns, TSpreadsheetColumn } from "@plane/types";
// components
import {
Expand Down Expand Up @@ -66,16 +65,16 @@ export const getScopeMemberIds = ({ isWorkspaceLevel, projectId }: TGetColumns):
export const getTeamProjectColumns = (): IGroupByColumn[] | undefined => undefined;

export const SpreadSheetPropertyIconMap: Record<string, FC<ISvgIcons>> = {
Users: Users,
MembersPropertyIcon: MembersPropertyIcon,
CalenderDays: CalendarDays,
CalenderCheck2: CalendarCheck2,
Triangle: Triangle,
Tag: Tag,
DueDatePropertyIcon: DueDatePropertyIcon,
EstimatePropertyIcon: EstimatePropertyIcon,
LabelPropertyIcon: LabelPropertyIcon,
ModuleIcon: ModuleIcon,
ContrastIcon: CycleIcon,
Signal: Signal,
CalendarClock: CalendarClock,
DoubleCircleIcon: DoubleCircleIcon,
PriorityPropertyIcon: PriorityPropertyIcon,
StartDatePropertyIcon: StartDatePropertyIcon,
StatePropertyIcon: StatePropertyIcon,
Link2: Link2,
Paperclip: Paperclip,
LayersIcon: LayersIcon,
Expand Down
6 changes: 3 additions & 3 deletions apps/web/ce/components/relations/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CircleDot, CopyPlus, XCircle } from "lucide-react";
import { RelatedIcon } from "@plane/propel/icons";
import { CircleDot, XCircle } from "lucide-react";
import { RelatedIcon, DuplicatePropertyIcon } from "@plane/propel/icons";
import type { TRelationObject } from "@/components/issues/issue-detail-widgets/relations";
import type { TIssueRelationTypes } from "../../types";

Expand All @@ -17,7 +17,7 @@ export const ISSUE_RELATION_OPTIONS: Record<TIssueRelationTypes, TRelationObject
key: "duplicate",
i18n_label: "issue.relation.duplicate",
className: "bg-custom-background-80 text-custom-text-200",
icon: (size) => <CopyPlus size={size} className="text-custom-text-200" />,
icon: (size) => <DuplicatePropertyIcon width={size} height={size} className="text-custom-text-200" />,
placeholder: "None",
},
blocked_by: {
Expand Down
Loading
Loading