[WEB-3751] chore: work item state icon improvement (#6960)

* chore: return order based on group

* chore: order for workspace stats endpoint

* chore: state response updated

* chore: state icon types updated

* chore: state icon updated

* chore: state settings new icon implementation

* chore: icon implementation

* chore: code refactor

* chore: code refactor

* chore: code refactor

* fix: order field type

---------

Co-authored-by: sangeethailango <sangeethailango21@gmail.com>
This commit is contained in:
Anmol Singh Bhatia 2025-04-29 14:33:53 +05:30 committed by GitHub
parent baabb82669
commit f5449c8f93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 376 additions and 145 deletions

View file

@ -2,6 +2,7 @@ import { Command } from "cmdk";
import { observer } from "mobx-react";
import { Check } from "lucide-react";
// plane imports
import { EIconSize } from "@plane/constants";
import { Spinner, StateGroupIcon } from "@plane/ui";
// store hooks
import { useProjectState } from "@/hooks/store";
@ -26,7 +27,12 @@ export const ChangeWorkItemStateList = observer((props: TChangeWorkItemStateList
projectStates.map((state) => (
<Command.Item key={state.id} onSelect={() => handleStateChange(state.id)} className="focus:outline-none">
<div className="flex items-center space-x-3">
<StateGroupIcon stateGroup={state.group} color={state.color} height="16px" width="16px" />
<StateGroupIcon
stateGroup={state.group}
color={state.color}
size={EIconSize.LG}
percentage={state?.order}
/>
<p>{state.name}</p>
</div>
<div>{state.id === currentStateId && <Check className="h-3 w-3" />}</div>

View file

@ -6,7 +6,7 @@ import { useParams } from "next/navigation";
// icons
import { ArchiveX } from "lucide-react";
// types
import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { PROJECT_AUTOMATION_MONTHS, EUserPermissions, EUserPermissionsLevel, EIconSize } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { IProject } from "@plane/types";
// ui
@ -42,7 +42,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
query: state.name,
content: (
<div className="flex items-center gap-2">
<StateGroupIcon stateGroup={state.group} color={state.color} height="16px" width="16px" />
<StateGroupIcon stateGroup={state.group} color={state.color} size={EIconSize.LG} />
{state.name}
</div>
),
@ -139,7 +139,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
</div>
</div>
<div className="flex w-full items-center justify-between gap-2 px-5 py-4">
<div className="ppy sm:py-10 flex w-full items-center justify-between gap-2 px-5 py-4">
<div className="w-1/2 text-sm font-medium">
{t("project_settings.automations.auto-close.auto_close_status")}
</div>
@ -149,18 +149,12 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
label={
<div className="flex items-center gap-2">
{selectedOption ? (
<StateGroupIcon
stateGroup={selectedOption.group}
color={selectedOption.color}
height="16px"
width="16px"
/>
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} size="lg" />
) : currentDefaultState ? (
<StateGroupIcon
stateGroup={currentDefaultState.group}
color={currentDefaultState.color}
height="16px"
width="16px"
size={EIconSize.LG}
/>
) : (
<DoubleCircleIcon className="h-3.5 w-3.5 text-custom-text-200" />

View file

@ -98,7 +98,12 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
query: `${state?.name}`,
content: (
<div className="flex items-center gap-2">
<StateGroupIcon stateGroup={state?.group ?? "backlog"} color={state?.color} className="h-3 w-3 flex-shrink-0" />
<StateGroupIcon
stateGroup={state?.group ?? "backlog"}
color={state?.color}
className="size-4 flex-shrink-0"
percentage={state?.order}
/>
<span className="flex-grow truncate text-left">{state?.name}</span>
</div>
),
@ -179,7 +184,8 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
<StateGroupIcon
stateGroup={selectedState?.group ?? "backlog"}
color={selectedState?.color ?? "rgba(var(--color-text-300))"}
className="h-3 w-3 flex-shrink-0"
className="size-4 flex-shrink-0"
percentage={selectedState?.order}
/>
)}
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (

View file

@ -95,7 +95,12 @@ export const RecentIssue = observer((props: BlockProps) => {
<div className="flex gap-4">
<Tooltip tooltipHeading="State" tooltipContent={state?.name ?? "State"}>
<div>
<StateGroupIcon stateGroup={state?.group ?? "backlog"} color={state?.color} className="h-4 w-4 my-auto" />
<StateGroupIcon
stateGroup={state?.group ?? "backlog"}
color={state?.color}
className="h-4 w-4 my-auto"
percentage={state?.order}
/>
</div>
</Tooltip>
<Tooltip tooltipHeading="Priority" tooltipContent={issueDetails?.priority ?? "Priority"}>

View file

@ -3,6 +3,7 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { X } from "lucide-react";
import { EIconSize } from "@plane/constants";
import { StateGroupIcon, Tag } from "@plane/ui";
// hooks
import { useProjectInbox, useProjectState } from "@/hooks/store";
@ -30,7 +31,7 @@ export const InboxIssueAppliedFiltersState: FC = observer(() => {
return (
<div key={value} className="relative flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<div className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden">
<StateGroupIcon color={optionDetail.color} stateGroup={optionDetail.group} height="12px" width="12px" />
<StateGroupIcon color={optionDetail.color} stateGroup={optionDetail.group} size={EIconSize.SM} />
</div>
<div className="text-xs truncate">{optionDetail?.name}</div>
<div

View file

@ -2,6 +2,7 @@
import { FC, useState } from "react";
import { observer } from "mobx-react";
import { EIconSize } from "@plane/constants";
import { IState } from "@plane/types";
import { Loader, StateGroupIcon } from "@plane/ui";
// components
@ -55,7 +56,14 @@ export const FilterState: FC<Props> = observer((props) => {
key={state?.id}
isChecked={filterValue?.includes(state?.id) ? true : false}
onClick={() => handleInboxIssueFilters("state", handleFilterValue(state.id))}
icon={<StateGroupIcon color={state.color} stateGroup={state.group} height="12px" width="12px" />}
icon={
<StateGroupIcon
color={state.color}
stateGroup={state.group}
size={EIconSize.SM}
percentage={state?.order}
/>
}
title={state.name}
/>
))}

View file

@ -4,6 +4,7 @@ import { observer } from "mobx-react";
// icons
import { X } from "lucide-react";
import { EIconSize } from "@plane/constants";
import { TStateGroups } from "@plane/types";
import { StateGroupIcon } from "@plane/ui";
@ -19,7 +20,7 @@ export const AppliedStateGroupFilters: React.FC<Props> = observer((props) => {
<>
{values.map((stateGroup) => (
<div key={stateGroup} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<StateGroupIcon stateGroup={stateGroup as TStateGroups} height="12px" width="12px" />
<StateGroupIcon stateGroup={stateGroup as TStateGroups} size={EIconSize.SM} />
{stateGroup}
<button
type="button"

View file

@ -5,6 +5,7 @@ import { observer } from "mobx-react";
// icons
import { X } from "lucide-react";
import { IState } from "@plane/types";
import { EIconSize } from "@plane/constants";
import { StateGroupIcon } from "@plane/ui";
// types
@ -27,7 +28,12 @@ export const AppliedStateFilters: React.FC<Props> = observer((props) => {
return (
<div key={stateId} className="flex items-center gap-1 rounded bg-custom-background-80 p-1 text-xs">
<StateGroupIcon color={stateDetails.color} stateGroup={stateDetails.group} height="12px" width="12px" />
<StateGroupIcon
color={stateDetails.color}
stateGroup={stateDetails.group}
size={EIconSize.SM}
percentage={stateDetails?.order}
/>
{stateDetails.name}
{editable && (
<button

View file

@ -3,6 +3,7 @@
import React, { useMemo, useState } from "react";
import sortBy from "lodash/sortBy";
import { observer } from "mobx-react";
import { EIconSize } from "@plane/constants";
import { IState } from "@plane/types";
// components
import { Loader, StateGroupIcon } from "@plane/ui";
@ -56,7 +57,14 @@ export const FilterState: React.FC<Props> = observer((props) => {
key={state.id}
isChecked={appliedFilters?.includes(state.id) ? true : false}
onClick={() => handleUpdate(state.id)}
icon={<StateGroupIcon stateGroup={state.group} color={state.color} />}
icon={
<StateGroupIcon
stateGroup={state.group}
color={state.color}
size={EIconSize.MD}
percentage={state?.order}
/>
}
title={state.name}
/>
))}

View file

@ -11,7 +11,7 @@ import uniq from "lodash/uniq";
import scrollIntoView from "smooth-scroll-into-view-if-needed";
import { ContrastIcon } from "lucide-react";
// plane types
import { EIssuesStoreType, ISSUE_PRIORITIES, STATE_GROUPS } from "@plane/constants";
import { EIconSize, EIssuesStoreType, ISSUE_PRIORITIES, STATE_GROUPS } from "@plane/constants";
import {
GroupByColumnTypes,
IGroupByColumn,
@ -198,8 +198,8 @@ const getStateColumns = (): IGroupByColumn[] | undefined => {
id: state.id,
name: state.name,
icon: (
<div className="h-3.5 w-3.5 rounded-full">
<StateGroupIcon stateGroup={state.group} color={state.color} width="14" height="14" />
<div className="size-4 rounded-full">
<StateGroupIcon stateGroup={state.group} color={state.color} size={EIconSize.LG} percentage={state.order} />
</div>
),
payload: { state_id: state.id },
@ -213,8 +213,8 @@ const getStateGroupColumns = (): IGroupByColumn[] => {
id: stateGroup.key,
name: stateGroup.label,
icon: (
<div className="h-3.5 w-3.5 rounded-full">
<StateGroupIcon stateGroup={stateGroup.key} width="14" height="14" />
<div className="size-4 rounded-full">
<StateGroupIcon stateGroup={stateGroup.key} size={EIconSize.LG} />
</div>
),
payload: {},

View file

@ -4,6 +4,7 @@ import { FC, useState, useRef } from "react";
import { observer } from "mobx-react";
import { ChevronDown, Plus } from "lucide-react";
// plane imports
import { EIconSize } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { IState, TStateGroups, TStateOperationsCallbacks } from "@plane/types";
import { StateGroupIcon } from "@plane/ui";
@ -74,7 +75,7 @@ export const GroupItem: FC<TGroupItem> = observer((props) => {
<ChevronDown className="w-4 h-4" />
</div>
<div className="flex-shrink-0 w-6 h-6 rounded flex justify-center items-center overflow-hidden">
<StateGroupIcon stateGroup={groupKey} height="16px" width="16px" />
<StateGroupIcon stateGroup={groupKey} size={EIconSize.XL} />
</div>
<div className="text-base font-medium text-custom-text-200 capitalize px-1">{groupKey}</div>
</div>

View file

@ -2,9 +2,11 @@ import { SetStateAction } from "react";
import { observer } from "mobx-react";
import { GripVertical, Pencil } from "lucide-react";
// plane imports
import { EIconSize } from "@plane/constants";
import { IState, TStateOperationsCallbacks } from "@plane/types";
import { StateGroupIcon } from "@plane/ui";
// local imports
import { useProjectState } from "@/hooks/store";
import { StateDelete, StateMarksAsDefault } from "./options";
type TBaseStateItemTitleProps = {
@ -28,6 +30,11 @@ export type TStateItemTitleProps = TEnabledStateItemTitleProps | TDisabledStateI
export const StateItemTitle = observer((props: TStateItemTitleProps) => {
const { stateCount, setUpdateStateModal, disabled, state, shouldShowDescription = true } = props;
// store hooks
const { getStatePercentageInGroup } = useProjectState();
// derived values
const statePercentage = getStatePercentageInGroup(state.id);
const percentage = statePercentage ? statePercentage / 100 : undefined;
return (
<div className="flex items-center gap-2 w-full justify-between">
@ -40,7 +47,7 @@ export const StateItemTitle = observer((props: TStateItemTitleProps) => {
)}
{/* state icon */}
<div className="flex-shrink-0">
<StateGroupIcon stateGroup={state.group} color={state.color} className={"size-3.5"} />
<StateGroupIcon stateGroup={state.group} color={state.color} size={EIconSize.XL} percentage={percentage} />
</div>
{/* state title and description */}
<div className="text-sm px-2 min-h-5">

View file

@ -45,6 +45,8 @@ export interface IStateStore {
stateId: string,
payload: Partial<IState>
) => Promise<void>;
getStatePercentageInGroup: (stateId: string | null | undefined) => number | undefined;
}
export class StateStore implements IStateStore {
@ -303,4 +305,27 @@ export class StateStore implements IStateStore {
});
}
};
/**
* Returns the percentage position of a state within its group based on sequence
* @param stateId The ID of the state to find the percentage for
* @returns The percentage position of the state in its group (0-100), or -1 if not found
*/
getStatePercentageInGroup = computedFn((stateId: string | null | undefined) => {
if (!stateId || !this.stateMap[stateId]) return -1;
const state = this.stateMap[stateId];
const group = state.group;
if (!group || !this.groupedProjectStates || !this.groupedProjectStates[group]) return -1;
// Get all states in the same group
const statesInGroup = this.groupedProjectStates[group];
const stateIndex = statesInGroup.findIndex((s) => s.id === stateId);
if (stateIndex === -1) return undefined;
// Calculate percentage: ((index + 1) / totalLength) * 100
return ((stateIndex + 1) / statesInGroup.length) * 100;
});
}