UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: контроль цветов и реворк интерфейса проектов

This commit is contained in:
DCCONSTRUCTIONS
2026-05-01 12:13:47 +03:00
parent a7ab8ee123
commit d7260bdfce
10 changed files with 700 additions and 96 deletions
@@ -7,6 +7,7 @@
import { useEffect, useRef, useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
// plane imports
import { Row } from "@plane/ui";
// components
@@ -25,8 +26,13 @@ export const AppHeader = observer(function AppHeader(props: AppHeaderProps) {
const { header, mobileHeader, className, rowClassName } = props;
const containerRef = useRef<HTMLDivElement>(null);
const [dockStyle, setDockStyle] = useState<CSSProperties | undefined>(undefined);
const pathname = usePathname();
const { workspaceSlug } = useParams();
const { data: userProfile } = useUserProfile();
const isCompactToolbar = userProfile?.theme?.nodedcCompactToolbar === true;
const workspaceSlugValue = workspaceSlug?.toString();
const isWorkspaceHome =
!!workspaceSlugValue && (pathname === `/${workspaceSlugValue}` || pathname === `/${workspaceSlugValue}/`);
const effectiveDockStyle = isCompactToolbar
? dockStyle
: {
@@ -49,10 +55,7 @@ export const AppHeader = observer(function AppHeader(props: AppHeaderProps) {
width,
});
document.documentElement.style.setProperty(
"--nodedc-bottom-dock-offset",
`${Math.max(height, 0)}px`
);
document.documentElement.style.setProperty("--nodedc-bottom-dock-offset", `${Math.max(height, 0)}px`);
};
updateDockBounds();
@@ -73,9 +76,10 @@ export const AppHeader = observer(function AppHeader(props: AppHeaderProps) {
ref={containerRef}
className={cn(
"fixed bottom-0 z-[18]",
isCompactToolbar ? "right-0 nodedc-app-header-compact" : "nodedc-app-header-expanded",
isCompactToolbar ? "nodedc-app-header-compact right-0" : "nodedc-app-header-expanded",
className
)}
data-nodedc-footer-scrim={isWorkspaceHome ? "false" : "true"}
style={effectiveDockStyle}
>
<Row
@@ -87,7 +91,7 @@ export const AppHeader = observer(function AppHeader(props: AppHeaderProps) {
<ExtendedAppHeader header={header} />
<div className="nodedc-bottom-dock-voice-slot" data-nodedc-voice-task-dock-slot />
</Row>
{mobileHeader && mobileHeader}
{mobileHeader ?? null}
</div>
);
});
@@ -35,6 +35,12 @@ import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-st
// types
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { calculateIdentifierWidth } from "../utils";
import {
applyNodedcListPropertiesWidth,
NODEDC_LIST_PROPERTIES_WIDTH_CSS_VAR,
NODEDC_LIST_PROPERTIES_WIDTH_DEFAULT,
persistNodedcListPropertiesWidth,
} from "./list-properties-width";
import type { TRenderQuickActions } from "./list-view-types";
interface IssueBlockProps {
@@ -157,6 +163,44 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
}
};
const handlePropertiesResizePointerDown = (event: React.PointerEvent<HTMLSpanElement>) => {
if (event.button !== 0 || typeof window === "undefined") return;
event.preventDefault();
event.stopPropagation();
const root = document.documentElement;
const computedWidth = Number.parseFloat(
getComputedStyle(root).getPropertyValue(NODEDC_LIST_PROPERTIES_WIDTH_CSS_VAR)
);
const initialWidth = Number.isFinite(computedWidth) ? computedWidth : NODEDC_LIST_PROPERTIES_WIDTH_DEFAULT;
const initialClientX = event.clientX;
let latestWidth = initialWidth;
const previousCursor = document.body.style.cursor;
const previousUserSelect = document.body.style.userSelect;
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
const handlePointerMove = (moveEvent: PointerEvent) => {
moveEvent.preventDefault();
latestWidth = applyNodedcListPropertiesWidth(initialWidth - (moveEvent.clientX - initialClientX));
};
const handlePointerUp = () => {
persistNodedcListPropertiesWidth(latestWidth);
document.body.style.cursor = previousCursor;
document.body.style.userSelect = previousUserSelect;
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
window.removeEventListener("pointercancel", handlePointerUp);
};
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
window.addEventListener("pointercancel", handlePointerUp);
};
// Calculate width for: projectIdentifier + "-" + dynamic sequence number digits
// Use next_work_item_sequence from backend (static value from project endpoint)
const maxSequenceId = currentProjectNextSequenceId ?? 1;
@@ -184,7 +228,7 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
<Row
ref={issueRef}
className={cn(
"group/list-block relative flex min-h-11 flex-col gap-3 bg-layer-transparent py-3 text-13 transition-colors hover:bg-layer-transparent-hover",
"nodedc-list-work-item-row group/list-block relative flex min-h-11 flex-col gap-3 bg-layer-transparent py-3 text-13 transition-colors hover:bg-layer-transparent-hover",
{
"border-accent-strong": getIsIssuePeeked(issue.id) && peekIssue?.nestingLevel === nestingLevel,
"border-strong-1": isIssueActive,
@@ -207,8 +251,19 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
}
}}
>
<div className="flex w-full gap-2 truncate">
<div className="flex flex-grow items-center gap-0.5 truncate">
<span
className="nodedc-list-properties-resize-handle"
role="separator"
aria-orientation="vertical"
aria-label="Resize list properties panel"
onPointerDown={handlePropertiesResizePointerDown}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
}}
/>
<div className="nodedc-list-work-item-main flex w-full gap-2 truncate">
<div className="flex min-w-0 flex-grow items-center gap-0.5 truncate">
<div className="flex items-center gap-1" style={isSubIssue ? { marginLeft } : {}}>
{/* select checkbox */}
{projectId && canSelectIssues && !isEpic && (
@@ -282,7 +337,9 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
disabled={isCurrentBlockDragging}
renderByDefault={false}
>
<p className="cursor-pointer truncate text-body-xs-medium text-primary">{issue.name}</p>
<p className="nodedc-list-work-item-title cursor-pointer truncate text-body-xs-medium text-primary">
{issue.name}
</p>
</Tooltip>
{isEpic && displayProperties && (
<WithDisplayPropertiesHOC
@@ -308,11 +365,11 @@ export const IssueBlock = observer(function IssueBlock(props: IssueBlockProps) {
</div>
)}
</div>
<div className="flex flex-shrink-0 items-center gap-2">
<div className="nodedc-list-work-item-side flex flex-shrink-0 items-center gap-2">
{!issue?.tempId ? (
<>
<IssueProperties
className={`relative flex flex-wrap ${isSidebarCollapsed ? "md:flex-shrink-0 md:flex-grow" : "lg:flex-shrink-0 lg:flex-grow"} items-center gap-2 whitespace-nowrap`}
className={`nodedc-list-work-item-properties relative flex flex-wrap ${isSidebarCollapsed ? "md:flex-shrink-0 md:flex-grow" : "lg:flex-shrink-0 lg:flex-grow"} items-center gap-2 whitespace-nowrap`}
issue={issue}
isReadOnly={!canEditIssueProperties}
updateIssue={updateIssue}
@@ -34,6 +34,7 @@ import { useBulkOperationStatus } from "@/plane-web/hooks/use-bulk-operation-sta
import type { GroupDropLocation } from "../utils";
import { getGroupByColumns, isWorkspaceLevel, isSubGrouped } from "../utils";
import { ListGroup } from "./list-group";
import { applyStoredNodedcListPropertiesWidth } from "./list-properties-width";
import type { TRenderQuickActions } from "./list-view-types";
export interface IList {
@@ -107,6 +108,17 @@ export const List = observer(function List(props: IList) {
);
}, [containerRef]);
useEffect(() => {
if (typeof window === "undefined") return;
const syncPropertiesWidth = () => applyStoredNodedcListPropertiesWidth();
syncPropertiesWidth();
window.addEventListener("resize", syncPropertiesWidth);
return () => window.removeEventListener("resize", syncPropertiesWidth);
}, []);
if (!groups) return null;
const getGroupIndex = (groupId: string | undefined) => groups.findIndex(({ id }) => id === groupId);
@@ -252,6 +252,12 @@ export const ListGroup = observer(function ListGroup(props: Props) {
const isGroupByCreatedBy = group_by === "created_by";
const shouldExpand = (!!groupIssueCount && isExpanded) || !group_by;
const shouldShowQuickAdd =
enableIssueQuickAdd &&
!disableIssueCreation &&
!isGroupByCreatedBy &&
!isCompletedCycle &&
!isWorkflowIssueCreationDisabled;
return validateEmptyIssueGroups(groupIssueCount) ? (
<div
@@ -295,51 +301,49 @@ export const ListGroup = observer(function ListGroup(props: Props) {
isDraggingOverColumn={isDraggingOverColumn}
isEpic={isEpic}
/>
{groupIssueIds && (
<IssueBlocksList
issueIds={groupIssueIds}
groupId={group.id}
issuesMap={issuesMap}
updateIssue={updateIssue}
quickActions={quickActions}
displayProperties={displayProperties}
canEditProperties={canEditProperties}
containerRef={containerRef}
isDragAllowed={isDragAllowed}
canDropOverIssue={!canOverlayBeVisible}
selectionHelpers={selectionHelpers}
isEpic={isEpic}
/>
)}
{shouldLoadMore &&
(group_by ? (
<>{loadMore}</>
) : (
<>
{Array.from({ length: 2 }).map((_, index) => (
<ListLoaderItemRow key={index} />
))}
<ListLoaderItemRow ref={setIntersectionElement} />
</>
))}
{enableIssueQuickAdd &&
!disableIssueCreation &&
!isGroupByCreatedBy &&
!isCompletedCycle &&
!isWorkflowIssueCreationDisabled && (
<div className="nodedc-bottom-dock-sticky-offset sticky z-[1] w-full flex-shrink-0">
<QuickAddIssueRoot
layout={EIssueLayoutTypes.LIST}
QuickAddButton={ListQuickAddIssueButton}
prePopulatedData={prePopulateQuickAddData(group_by, group.id)}
containerClassName="border-b border-t border-subtle bg-surface-1 "
quickAddCallback={quickAddCallback}
isEpic={isEpic}
/>
</div>
<div className={cn({ "nodedc-bottom-dock-aware-padding": shouldShowQuickAdd })}>
{groupIssueIds && (
<IssueBlocksList
issueIds={groupIssueIds}
groupId={group.id}
issuesMap={issuesMap}
updateIssue={updateIssue}
quickActions={quickActions}
displayProperties={displayProperties}
canEditProperties={canEditProperties}
containerRef={containerRef}
isDragAllowed={isDragAllowed}
canDropOverIssue={!canOverlayBeVisible}
selectionHelpers={selectionHelpers}
isEpic={isEpic}
/>
)}
{shouldLoadMore &&
(group_by ? (
<>{loadMore}</>
) : (
<>
{Array.from({ length: 2 }).map((_, index) => (
<ListLoaderItemRow key={index} />
))}
<ListLoaderItemRow ref={setIntersectionElement} />
</>
))}
</div>
{shouldShowQuickAdd && (
<div className="nodedc-list-quick-add-sticky nodedc-bottom-dock-sticky-offset sticky z-[2] w-full flex-shrink-0 bg-transparent py-0.5">
<QuickAddIssueRoot
layout={EIssueLayoutTypes.LIST}
QuickAddButton={ListQuickAddIssueButton}
prePopulatedData={prePopulateQuickAddData(group_by, group.id)}
containerClassName="border-b border-t border-subtle bg-surface-1 "
quickAddCallback={quickAddCallback}
isEpic={isEpic}
/>
</div>
)}
</div>
)}
</div>
@@ -0,0 +1,46 @@
export const NODEDC_LIST_PROPERTIES_WIDTH_STORAGE_KEY = "nodedc_project_list_properties_width";
export const NODEDC_LIST_PROPERTIES_WIDTH_CSS_VAR = "--nodedc-list-properties-panel-width";
export const NODEDC_LIST_PROPERTIES_WIDTH_DEFAULT = 1024;
export const NODEDC_LIST_PROPERTIES_WIDTH_MIN = 760;
export const NODEDC_LIST_PROPERTIES_WIDTH_MAX = 1280;
const getRuntimeMaxWidth = () => {
if (typeof window === "undefined") return NODEDC_LIST_PROPERTIES_WIDTH_MAX;
return Math.max(
NODEDC_LIST_PROPERTIES_WIDTH_MIN,
Math.min(NODEDC_LIST_PROPERTIES_WIDTH_MAX, window.innerWidth - 420)
);
};
export const clampNodedcListPropertiesWidth = (width: number) => {
const maxWidth = getRuntimeMaxWidth();
const normalizedWidth = Number.isFinite(width) ? width : NODEDC_LIST_PROPERTIES_WIDTH_DEFAULT;
return Math.min(Math.max(normalizedWidth, NODEDC_LIST_PROPERTIES_WIDTH_MIN), maxWidth);
};
export const applyNodedcListPropertiesWidth = (width: number) => {
if (typeof document === "undefined") return width;
const clampedWidth = clampNodedcListPropertiesWidth(width);
document.documentElement.style.setProperty(NODEDC_LIST_PROPERTIES_WIDTH_CSS_VAR, `${clampedWidth}px`);
return clampedWidth;
};
export const applyStoredNodedcListPropertiesWidth = () => {
if (typeof window === "undefined") return NODEDC_LIST_PROPERTIES_WIDTH_DEFAULT;
const storedWidth = Number(window.localStorage.getItem(NODEDC_LIST_PROPERTIES_WIDTH_STORAGE_KEY));
return applyNodedcListPropertiesWidth(
Number.isFinite(storedWidth) && storedWidth > 0 ? storedWidth : NODEDC_LIST_PROPERTIES_WIDTH_DEFAULT
);
};
export const persistNodedcListPropertiesWidth = (width: number) => {
if (typeof window === "undefined") return;
window.localStorage.setItem(NODEDC_LIST_PROPERTIES_WIDTH_STORAGE_KEY, String(clampNodedcListPropertiesWidth(width)));
};
@@ -59,7 +59,7 @@ export interface IIssueProperties {
}
export const IssueProperties = observer(function IssueProperties(props: IIssueProperties) {
const { issue, updateIssue, displayProperties, isReadOnly, className, isEpic = false } = props;
const { issue, updateIssue, displayProperties, isReadOnly, className, activeLayout, isEpic = false } = props;
// i18n
const { t } = useTranslation();
// store hooks
@@ -175,6 +175,15 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
const redirectToIssueDetail = () => router.push(`${workItemLink}#sub-issues`);
if (!displayProperties || !issue.project_id) return null;
const isListLayout = activeLayout === "List";
const propertySlotClassName = (slot: string, ...classNames: (string | false | null | undefined)[]) =>
cn(
"h-5",
isListLayout && "nodedc-list-property-slot",
isListLayout && `nodedc-list-property-${slot}`,
...classNames
);
const listIconControlClassName = isListLayout ? "nodedc-list-icon-control" : undefined;
// date range is enabled only when both dates are available and both dates are enabled
const isDateRangeEnabled: boolean = Boolean(
@@ -196,7 +205,11 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
{/* basic properties */}
{/* state */}
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="state">
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName("state")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<StateDropdown
buttonContainerClassName="truncate max-w-40"
value={issue.state_id}
@@ -212,12 +225,17 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
{/* priority */}
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="priority">
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName("priority", isListLayout && "nodedc-list-property-icon-only")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<PriorityDropdown
value={issue?.priority}
onChange={handlePriority}
disabled={isReadOnly}
buttonVariant="border-without-text"
buttonContainerClassName={listIconControlClassName}
renderByDefault={isMobile}
showTooltip
/>
@@ -230,7 +248,11 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
displayPropertyKey={["start_date", "due_date"]}
shouldRenderProperty={() => isDateRangeEnabled}
>
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName("date-range")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<DateRangeDropdown
value={{
from: getDate(issue.start_date) || undefined,
@@ -255,6 +277,7 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
showTooltip
renderPlaceholder={false}
customTooltipHeading="Date Range"
buttonContainerClassName={isListLayout ? "nodedc-list-valued-date-control" : undefined}
/>
</div>
</WithDisplayPropertiesHOC>
@@ -265,7 +288,14 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
displayPropertyKey="start_date"
shouldRenderProperty={() => !isDateRangeEnabled}
>
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName(
"start-date",
isListLayout && (issue.start_date ? "nodedc-list-property-valued-date" : "nodedc-list-property-icon-only")
)}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<DateDropdown
value={issue.start_date ?? null}
rangePreview={{
@@ -277,6 +307,7 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
placeholder={t("common.order_by.start_date")}
icon={<StartDatePropertyIcon className="h-3 w-3 flex-shrink-0" />}
buttonVariant={issue.start_date ? "border-with-text" : "border-without-text"}
buttonContainerClassName={issue.start_date ? undefined : listIconControlClassName}
optionsClassName="z-10"
disabled={isReadOnly}
renderByDefault={isMobile}
@@ -292,7 +323,14 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
displayPropertyKey="due_date"
shouldRenderProperty={() => !isDateRangeEnabled}
>
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName(
"due-date",
isListLayout && (issue.target_date ? "nodedc-list-property-valued-date" : "nodedc-list-property-icon-only")
)}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<DateDropdown
value={issue?.target_date ?? null}
rangePreview={{
@@ -304,6 +342,7 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
placeholder={t("common.order_by.due_date")}
icon={<DueDatePropertyIcon className="h-3 w-3 shrink-0" />}
buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"}
buttonContainerClassName={issue.target_date ? undefined : listIconControlClassName}
buttonClassName={
shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group) ? "text-danger-primary" : ""
}
@@ -319,7 +358,11 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
{/* assignee */}
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="assignee">
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName("assignee")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<MemberDropdown
projectId={issue?.project_id}
value={issue?.assignee_ids}
@@ -343,7 +386,11 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
{/* modules */}
{projectDetails?.module_view && (
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="modules">
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName("module")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<ModuleDropdown
buttonContainerClassName="truncate max-w-40"
projectId={issue?.project_id}
@@ -363,7 +410,11 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
{/* cycles */}
{projectDetails?.cycle_view && (
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="cycle">
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName("cycle")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<CycleDropdown
buttonContainerClassName="truncate max-w-40"
projectId={issue?.project_id}
@@ -384,7 +435,11 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
{/* estimates */}
{projectId && areEstimateEnabledByProjectId(projectId?.toString()) && (
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="estimate">
<div className="h-5" onFocus={handleEventPropagation} onClick={handleEventPropagation}>
<div
className={propertySlotClassName("estimate")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<EstimateDropdown
value={issue.estimate_point ?? undefined}
onChange={handleEstimate}
@@ -421,6 +476,7 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
}}
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1",
isListLayout && "nodedc-list-property-slot nodedc-list-property-sub-issues",
{
"cursor-pointer hover:bg-layer-1": subIssueCount,
}
@@ -446,7 +502,10 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
renderByDefault={false}
>
<div
className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1"
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1",
isListLayout && "nodedc-list-property-slot nodedc-list-property-attachments"
)}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
@@ -469,7 +528,10 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
renderByDefault={false}
>
<div
className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1"
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded-sm border-[0.5px] border-strong px-2.5 py-1",
isListLayout && "nodedc-list-property-slot nodedc-list-property-links"
)}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
@@ -484,16 +546,22 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
{/* label */}
<WithDisplayPropertiesHOC displayProperties={displayProperties} displayPropertyKey="labels">
<IssuePropertyLabels
projectId={issue?.project_id || null}
value={issue?.label_ids || []}
defaultOptions={defaultLabelOptions}
onChange={handleLabel}
disabled={isReadOnly}
renderByDefault={isMobile}
hideDropdownArrow
maxRender={3}
/>
<div
className={propertySlotClassName("labels")}
onFocus={handleEventPropagation}
onClick={handleEventPropagation}
>
<IssuePropertyLabels
projectId={issue?.project_id || null}
value={issue?.label_ids || []}
defaultOptions={defaultLabelOptions}
onChange={handleLabel}
disabled={isReadOnly}
renderByDefault={isMobile}
hideDropdownArrow
maxRender={3}
/>
</div>
</WithDisplayPropertiesHOC>
</div>
);