[WEB-5762] fix: workitem detail sidebar properties design consistency (#8400)

This commit is contained in:
Jayash Tripathy 2025-12-19 18:15:56 +05:30 committed by GitHub
parent 3876bf054c
commit 67c39dfc3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 101 additions and 62 deletions

View file

@ -19,7 +19,7 @@ export function SidebarPropertyListItem(props: TSidebarPropertyListItemProps) {
<span>{label}</span> <span>{label}</span>
{appendElement} {appendElement}
</div> </div>
<div className={cn("grow flex items-center flex-wrap gap-2", childrenClassName)}>{children}</div> <div className={cn("grow flex items-center flex-wrap gap-1", childrenClassName)}>{children}</div>
</div> </div>
); );
} }

View file

@ -1,5 +1,6 @@
import React from "react"; import React from "react";
// helpers // helpers
import { Button } from "@plane/propel/button";
import { Tooltip } from "@plane/propel/tooltip"; import { Tooltip } from "@plane/propel/tooltip";
import { cn } from "@plane/utils"; import { cn } from "@plane/utils";
// types // types
@ -71,9 +72,11 @@ function BorderButton(props: ButtonProps) {
isMobile={isMobile} isMobile={isMobile}
renderByDefault={renderToolTipByDefault} renderByDefault={renderToolTipByDefault}
> >
<div <Button
variant="ghost"
size="sm"
className={cn( className={cn(
"h-full w-full flex items-center gap-1.5 border-[0.5px] border-strong hover:bg-layer-transparent-hover text-11 px-2 py-0.5 rounded-sm", "h-full w-full flex items-center justify-start gap-1.5 border-[0.5px] border-strong",
{ {
"bg-layer-transparent-active": isActive, "bg-layer-transparent-active": isActive,
}, },
@ -81,7 +84,7 @@ function BorderButton(props: ButtonProps) {
)} )}
> >
{children} {children}
</div> </Button>
</Tooltip> </Tooltip>
); );
} }
@ -97,14 +100,16 @@ function BackgroundButton(props: ButtonProps) {
isMobile={isMobile} isMobile={isMobile}
renderByDefault={renderToolTipByDefault} renderByDefault={renderToolTipByDefault}
> >
<div <Button
variant="ghost"
size="sm"
className={cn( className={cn(
"h-full w-full flex items-center gap-1.5 rounded-sm text-11 px-2 py-0.5 bg-layer-3 hover:bg-layer-1-hover", "h-full w-full flex items-center justify-start gap-1.5 bg-layer-3 hover:bg-layer-1-hover",
className className
)} )}
> >
{children} {children}
</div> </Button>
</Tooltip> </Tooltip>
); );
} }
@ -120,9 +125,11 @@ function TransparentButton(props: ButtonProps) {
isMobile={isMobile} isMobile={isMobile}
renderByDefault={renderToolTipByDefault} renderByDefault={renderToolTipByDefault}
> >
<div <Button
variant="ghost"
size="sm"
className={cn( className={cn(
"h-full w-full flex items-center gap-1.5 rounded-sm text-11 px-2 py-0.5 hover:bg-layer-transparent-hover", "h-full w-full flex items-center justify-start gap-1.5",
{ {
"bg-layer-transparent-active": isActive, "bg-layer-transparent-active": isActive,
}, },
@ -130,7 +137,7 @@ function TransparentButton(props: ButtonProps) {
)} )}
> >
{children} {children}
</div> </Button>
</Tooltip> </Tooltip>
); );
} }

View file

@ -138,7 +138,9 @@ export const DateDropdown = observer(function DateDropdown(props: Props) {
> >
{!hideIcon && icon} {!hideIcon && icon}
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
<span className="flex-grow truncate">{value ? renderFormattedDate(value, formatToken) : placeholder}</span> <span className="flex-grow truncate text-left text-body-xs-medium">
{value ? renderFormattedDate(value, formatToken) : placeholder}
</span>
)} )}
{isClearable && !disabled && isDateSelected && ( {isClearable && !disabled && isDateSelected && (
<CloseIcon <CloseIcon

View file

@ -144,7 +144,7 @@ export const MemberDropdownBase = observer(function MemberDropdownBase(props: TM
> >
{!hideIcon && <ButtonAvatars showTooltip={showTooltip} userIds={value} icon={icon} />} {!hideIcon && <ButtonAvatars showTooltip={showTooltip} userIds={value} icon={icon} />}
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && ( {BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
<span className="flex-grow truncate leading-5"> <span className="flex-grow truncate leading-5 text-left text-body-xs-medium">
{getDisplayName(value, showUserDetails, placeholder)} {getDisplayName(value, showUserDetails, placeholder)}
</span> </span>
)} )}

View file

@ -120,7 +120,14 @@ function BorderButton(props: ButtonProps) {
<SignalHigh className="size-3" /> <SignalHigh className="size-3" />
))} ))}
{!hideText && ( {!hideText && (
<span className="flex-grow truncate text-caption-sm-regular">{priorityDetails?.title ?? placeholder}</span> <span
className={cn("flex-grow truncate text-body-xs-medium", {
"text-secondary": priority && priority !== "none",
"text-placeholder": !priority || priority === "none",
})}
>
{priorityDetails?.title ?? placeholder}
</span>
)} )}
{dropdownArrow && ( {dropdownArrow && (
<ChevronDownIcon className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" /> <ChevronDownIcon className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
@ -204,7 +211,12 @@ function BackgroundButton(props: ButtonProps) {
<SignalHigh className="size-3" /> <SignalHigh className="size-3" />
))} ))}
{!hideText && ( {!hideText && (
<span className="flex-grow truncate text-caption-sm-regular"> <span
className={cn("flex-grow truncate text-body-xs-medium", {
"text-secondary": priority && priority !== "none",
"text-placeholder": !priority || priority === "none",
})}
>
{priorityDetails?.title ?? t("common.priority") ?? placeholder} {priorityDetails?.title ?? t("common.priority") ?? placeholder}
</span> </span>
)} )}
@ -246,7 +258,7 @@ function TransparentButton(props: ButtonProps) {
> >
<div <div
className={cn( className={cn(
"h-full w-full flex items-center gap-1.5 rounded-sm px-2 py-0.5 hover:bg-layer-transparent-hover", "h-full w-full flex items-center gap-1.5 rounded-sm hover:bg-layer-transparent-hover px-2",
{ {
// compact the icons if text is hidden // compact the icons if text is hidden
"px-0.5": hideText, "px-0.5": hideText,
@ -283,7 +295,12 @@ function TransparentButton(props: ButtonProps) {
<SignalHigh className="size-3" /> <SignalHigh className="size-3" />
))} ))}
{!hideText && ( {!hideText && (
<span className="flex-grow truncate text-caption-sm-regular"> <span
className={cn("flex-grow truncate text-body-xs-medium", {
"text-secondary": priority && priority !== "none",
"text-placeholder": !priority || priority === "none",
})}
>
{priorityDetails?.title ?? t("common.priority") ?? placeholder} {priorityDetails?.title ?? t("common.priority") ?? placeholder}
</span> </span>
)} )}

View file

@ -50,8 +50,8 @@ export const IssueCycleSelect = observer(function IssueCycleSelect(props: TIssue
disabled={disableSelect} disabled={disableSelect}
buttonVariant="transparent-with-text" buttonVariant="transparent-with-text"
className="group w-full" className="group w-full"
buttonContainerClassName="w-full text-left rounded-sm" buttonContainerClassName="w-full text-left h-7.5 rounded-sm"
buttonClassName={`text-13 justify-between ${issue?.cycle_id ? "" : "text-placeholder"}`} buttonClassName={`text-body-xs-medium justify-between ${issue?.cycle_id ? "" : "text-placeholder"}`}
placeholder={t("cycle.no_cycle")} placeholder={t("cycle.no_cycle")}
hideIcon hideIcon
dropdownArrow dropdownArrow

View file

@ -1,6 +1,6 @@
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Button } from "@plane/propel/button";
import { CloseIcon, LabelFilledIcon } from "@plane/propel/icons"; import { CloseIcon, LabelFilledIcon } from "@plane/propel/icons";
import { cn } from "@plane/utils";
// types // types
import { useLabel } from "@/hooks/store/use-label"; import { useLabel } from "@/hooks/store/use-label";
import type { TLabelOperations } from "./root"; import type { TLabelOperations } from "./root";
@ -31,25 +31,14 @@ export const LabelListItem = observer(function LabelListItem(props: TLabelListIt
if (!label) return <></>; if (!label) return <></>;
return ( return (
<button <Button key={labelId} type="button" variant="tertiary" onClick={() => void handleLabel()} disabled={disabled}>
key={labelId}
type="button"
className={cn(
"h-full w-min flex items-center gap-1.5 rounded-sm px-2 py-0.5 bg-layer-transparent-active group text-body-xs-regular text-tertiary",
{
"cursor-pointer": !disabled,
}
)}
onClick={handleLabel}
disabled={disabled}
>
<LabelFilledIcon className="size-3" color={label.color ?? "#000000"} /> <LabelFilledIcon className="size-3" color={label.color ?? "#000000"} />
<div className="flex-shrink-0 text-body-xs-regular">{label.name}</div> <div className="flex-shrink-0">{label.name}</div>
{!disabled && ( {!disabled && (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<CloseIcon className="transition-all h-2.5 w-2.5 group-hover:text-danger" /> <CloseIcon className="transition-all h-2.5 w-2.5 group-hover:text-danger" />
</div> </div>
)} )}
</button> </Button>
); );
}); });

View file

@ -92,7 +92,7 @@ export const IssueLabel = observer(function IssueLabel(props: TIssueLabel) {
); );
return ( return (
<div className="relative flex flex-wrap items-center gap-1 min-h-7.5 w-full"> <div className="relative flex flex-wrap items-center gap-1 min-h-7.5 w-full px-2">
<LabelList <LabelList
workspaceSlug={workspaceSlug} workspaceSlug={workspaceSlug}
projectId={projectId} projectId={projectId}

View file

@ -1,11 +1,12 @@
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { usePopper } from "react-popper"; import { usePopper } from "react-popper";
import { Check, Loader, Search } from "lucide-react"; import { Check, Loader, Plus, Search } from "lucide-react";
import { Combobox } from "@headlessui/react"; import { Combobox } from "@headlessui/react";
// plane imports // plane imports
import { EUserPermissionsLevel, getRandomLabelColor } from "@plane/constants"; import { EUserPermissionsLevel, getRandomLabelColor } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import type { IIssueLabel } from "@plane/types"; import type { IIssueLabel } from "@plane/types";
import { EUserProjectRoles } from "@plane/types"; import { EUserProjectRoles } from "@plane/types";
// helpers // helpers
@ -84,11 +85,7 @@ export const IssueLabelSelect = observer(function IssueLabelSelect(props: IIssue
const issueLabels = values ?? []; const issueLabels = values ?? [];
const label = ( const label = <span className="text-body-xs-medium text-placeholder">{t("label.select")}</span>;
<span className="size-full flex items-center rounded-sm px-2 py-0.5 bg-layer-transparent hover:bg-layer-transparent-hover text-body-xs-regular text-tertiary">
{t("label.select")}
</span>
);
const searchInputKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => { const searchInputKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (query !== "" && e.key === "Escape") { if (query !== "" && e.key === "Escape") {
@ -123,14 +120,16 @@ export const IssueLabelSelect = observer(function IssueLabelSelect(props: IIssue
multiple multiple
> >
<Combobox.Button as={Fragment}> <Combobox.Button as={Fragment}>
<button <Button
ref={setReferenceElement} ref={setReferenceElement}
type="button" type="button"
className="cursor-pointer size-full" variant="tertiary"
size="sm"
prependIcon={<Plus />}
onClick={() => !projectLabels && fetchLabels()} onClick={() => !projectLabels && fetchLabels()}
> >
{label} {label}
</button> </Button>
</Combobox.Button> </Combobox.Button>
<Combobox.Options className="fixed z-10"> <Combobox.Options className="fixed z-10">

View file

@ -64,8 +64,8 @@ export const IssueModuleSelect = observer(function IssueModuleSelect(props: TIss
placeholder={t("module.no_module")} placeholder={t("module.no_module")}
disabled={disableSelect} disabled={disableSelect}
className="group h-full w-full" className="group h-full w-full"
buttonContainerClassName="w-full rounded-sm" buttonContainerClassName="w-full text-left h-7.5 rounded-sm"
buttonClassName={`min-h-8 text-13 justify-between ${issue?.module_ids?.length ? "" : "text-placeholder"}`} buttonClassName={`text-body-xs-medium justify-between ${issue?.module_ids?.length ? "" : "text-placeholder"}`}
buttonVariant="transparent-with-text" buttonVariant="transparent-with-text"
hideIcon hideIcon
dropdownArrow dropdownArrow

View file

@ -84,7 +84,7 @@ export const IssueParentSelect = observer(function IssueParentSelect(props: TIss
disabled={disabled} disabled={disabled}
> >
{issue.parent_id && parentIssue ? ( {issue.parent_id && parentIssue ? (
<div className="flex items-center gap-1 bg-success-subtle rounded-sm px-1.5 py-1"> <div className="flex items-center gap-1.5">
<Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name} isMobile={isMobile}> <Tooltip tooltipHeading="Title" tooltipContent={parentIssue.name} isMobile={isMobile}>
<Link href={workItemLink} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()}> <Link href={workItemLink} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()}>
{parentIssue?.project_id && parentIssueProjectDetails && ( {parentIssue?.project_id && parentIssueProjectDetails && (
@ -94,7 +94,7 @@ export const IssueParentSelect = observer(function IssueParentSelect(props: TIss
projectIdentifier={parentIssueProjectDetails?.identifier} projectIdentifier={parentIssueProjectDetails?.identifier}
issueSequenceId={parentIssue.sequence_id} issueSequenceId={parentIssue.sequence_id}
size="xs" size="xs"
variant="success" variant="secondary"
/> />
)} )}
</Link> </Link>
@ -115,7 +115,7 @@ export const IssueParentSelect = observer(function IssueParentSelect(props: TIss
)} )}
</div> </div>
) : ( ) : (
<span className="text-body-xs-regular text-placeholder">{t("issue.add.parent")}</span> <span className="text-body-xs-medium text-placeholder">{t("issue.add.parent")}</span>
)} )}
{!disabled && ( {!disabled && (
<span <span

View file

@ -75,7 +75,7 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
return ( return (
<div> <div>
<h6 className="text-body-xs-medium">{t("common.properties")}</h6> <h6 className="text-body-xs-medium">{t("common.properties")}</h6>
<div className={`w-full space-y-2 mt-3 ${disabled ? "opacity-60" : ""}`}> <div className={`w-full space-y-3 mt-3 ${disabled ? "opacity-60" : ""}`}>
<SidebarPropertyListItem icon={StatePropertyIcon} label={t("common.state")}> <SidebarPropertyListItem icon={StatePropertyIcon} label={t("common.state")}>
<StateDropdown <StateDropdown
value={issue?.state_id} value={issue?.state_id}
@ -85,7 +85,7 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
buttonVariant="transparent-with-text" buttonVariant="transparent-with-text"
className="w-full grow group" className="w-full grow group"
buttonContainerClassName="w-full text-left h-7.5" buttonContainerClassName="w-full text-left h-7.5"
buttonClassName="text-body-xs-regular" buttonClassName={`text-body-xs-medium ${issue?.state_id ? "" : "text-placeholder"}`}
dropdownArrow dropdownArrow
dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline"
/> />
@ -102,7 +102,7 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"} buttonVariant={issue?.assignee_ids?.length > 1 ? "transparent-without-text" : "transparent-with-text"}
className="w-full grow group" className="w-full grow group"
buttonContainerClassName="w-full text-left h-7.5" buttonContainerClassName="w-full text-left h-7.5"
buttonClassName={`text-body-xs-regular justify-between ${issue?.assignee_ids?.length > 0 ? "" : "text-placeholder"}`} buttonClassName={`text-body-xs-medium justify-between ${issue?.assignee_ids?.length > 0 ? "" : "text-placeholder"}`}
hideIcon={issue.assignee_ids?.length === 0} hideIcon={issue.assignee_ids?.length === 0}
dropdownArrow dropdownArrow
dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline" dropdownArrowClassName="h-3.5 w-3.5 hidden group-hover:inline"
@ -116,18 +116,22 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
disabled={disabled} disabled={disabled}
buttonVariant="transparent-with-text" buttonVariant="transparent-with-text"
className="w-full h-7.5 grow rounded-sm" className="w-full h-7.5 grow rounded-sm"
buttonContainerClassName="size-full text-left" buttonContainerClassName="w-full text-left h-7.5"
buttonClassName="size-full px-2 py-0.5 whitespace-nowrap [&_svg]:size-3.5" buttonClassName={`text-body-xs-medium whitespace-nowrap [&_svg]:size-3.5 ${!issue?.priority || issue?.priority === "none" ? "text-placeholder" : ""}`}
/> />
</SidebarPropertyListItem> </SidebarPropertyListItem>
{createdByDetails && ( {createdByDetails && (
<SidebarPropertyListItem icon={UserCirclePropertyIcon} label={t("common.created_by")}> <SidebarPropertyListItem
icon={UserCirclePropertyIcon}
label={t("common.created_by")}
childrenClassName="px-2"
>
<ButtonAvatars <ButtonAvatars
showTooltip showTooltip
userIds={createdByDetails?.display_name.includes("-intake") ? null : createdByDetails?.id} userIds={createdByDetails?.display_name.includes("-intake") ? null : createdByDetails?.id}
/> />
<span className="grow truncate leading-5"> <span className="grow truncate text-body-xs-medium text-secondary leading-5">
{createdByDetails?.display_name.includes("-intake") ? "Plane" : createdByDetails?.display_name} {createdByDetails?.display_name.includes("-intake") ? "Plane" : createdByDetails?.display_name}
</span> </span>
</SidebarPropertyListItem> </SidebarPropertyListItem>
@ -147,7 +151,7 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
disabled={disabled} disabled={disabled}
className="w-full grow group" className="w-full grow group"
buttonContainerClassName="w-full text-left h-7.5" buttonContainerClassName="w-full text-left h-7.5"
buttonClassName={`text-body-xs-regular ${issue?.start_date ? "" : "text-placeholder"}`} buttonClassName={`text-body-xs-medium ${issue?.start_date ? "" : "text-placeholder"}`}
hideIcon hideIcon
clearIconClassName="h-3 w-3 hidden group-hover:inline" clearIconClassName="h-3 w-3 hidden group-hover:inline"
/> />
@ -168,7 +172,7 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
disabled={disabled} disabled={disabled}
className="w-full grow group" className="w-full grow group"
buttonContainerClassName="w-full text-left h-7.5" buttonContainerClassName="w-full text-left h-7.5"
buttonClassName={cn("text-body-xs-regular", { buttonClassName={cn("text-body-xs-medium", {
"text-placeholder": !issue.target_date, "text-placeholder": !issue.target_date,
"text-danger": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group), "text-danger": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group),
})} })}
@ -189,7 +193,7 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
buttonVariant="transparent-with-text" buttonVariant="transparent-with-text"
className="w-full grow group" className="w-full grow group"
buttonContainerClassName="w-full text-left h-7.5" buttonContainerClassName="w-full text-left h-7.5"
buttonClassName={`text-body-xs-regular ${issue?.estimate_point !== undefined ? "" : "text-placeholder"}`} buttonClassName={`text-body-xs-medium ${issue?.estimate_point !== undefined ? "" : "text-placeholder"}`}
placeholder="None" placeholder="None"
hideIcon hideIcon
dropdownArrow dropdownArrow

View file

@ -34,6 +34,28 @@ export const FiltersToggle = observer(function FiltersToggle<P extends TFilterPr
filter.toggleVisibility(); filter.toggleVisibility();
}; };
// Base classes when filter is active
const activeFilterBaseClasses =
"text-accent-primary border border-accent-subtle-1 hover:border-accent-subtle-1 active:border-accent-subtle-1 focus:border-accent-subtle-1";
// State classes that prevent hover/active/focus color changes
const noHoverStateClasses = "hover:text-accent-primary active:text-accent-primary focus:text-accent-primary";
// Background classes based on toggle state (darker when open, lighter when closed)
const backgroundClasses = isFilterRowVisible
? "bg-accent-subtle-hover hover:bg-accent-subtle-hover active:bg-accent-subtle-hover focus:bg-accent-subtle-hover"
: "bg-accent-subtle hover:bg-accent-subtle active:bg-accent-subtle focus:bg-accent-subtle";
const buttonClassName = cn({
[activeFilterBaseClasses]: showFilterRowChangesPill,
[backgroundClasses]: showFilterRowChangesPill,
[noHoverStateClasses]: showFilterRowChangesPill,
});
const iconClassName = cn({
"text-accent-primary [&_path]:fill-current": showFilterRowChangesPill,
});
// Show the add filter button when there are no active conditions, the filter row is hidden, and no unsaved changes exist // Show the add filter button when there are no active conditions, the filter row is hidden, and no unsaved changes exist
if (filter && showAddFilterButton) { if (filter && showAddFilterButton) {
return ( return (
@ -55,9 +77,8 @@ export const FiltersToggle = observer(function FiltersToggle<P extends TFilterPr
variant="secondary" variant="secondary"
icon={showFilterRowChangesPill ? FilterAppliedIcon : FilterIcon} icon={showFilterRowChangesPill ? FilterAppliedIcon : FilterIcon}
onClick={handleToggleFilter} onClick={handleToggleFilter}
className={cn({ className={buttonClassName}
"text-accent-primary bg-accent-subtle border border-accent-subtle-1": showFilterRowChangesPill, iconClassName={iconClassName}
})}
/> />
); );
}); });

View file

@ -295,7 +295,7 @@
/* Background colors */ /* Background colors */
--background-color-canvas: var(--color-neutral-300); --background-color-canvas: var(--color-neutral-300);
--background-color-surface-1: var(--color-neutral-white); --background-color-surface-1: var(--color-neutral-white);
--background-color-surface-2: var(--color-neutral-100); --background-color-surface-2: var(--color-neutral-200);
--background-color-layer-1: var(--color-neutral-200); --background-color-layer-1: var(--color-neutral-200);
--background-color-layer-1-hover: var(--color-neutral-300); --background-color-layer-1-hover: var(--color-neutral-300);
--background-color-layer-1-active: var(--color-neutral-400); --background-color-layer-1-active: var(--color-neutral-400);