[WEB-5600] chore: project identifier char limit updated and table layout enhancements (#8263)

This commit is contained in:
Anmol Singh Bhatia 2025-12-08 20:00:54 +05:30 committed by GitHub
parent 7659997b53
commit f0bc2bd3bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 116 additions and 109 deletions

View file

@ -239,11 +239,6 @@ const IssueRowDetails = observer(function IssueRowDetails(props: IssueRowDetails
const canSelectIssues = !disableUserActions && !selectionHelpers.isSelectionDisabled;
//TODO: add better logic. This is to have a min width for ID/Key based on the length of project identifier
const keyMinWidth = displayProperties?.key
? (getProjectIdentifierById(issueDetail.project_id)?.length ?? 0 + 5) * 7
: 0;
const workItemLink = generateWorkItemLink({
workspaceSlug: workspaceSlug?.toString(),
projectId: issueDetail?.project_id,
@ -255,11 +250,12 @@ const IssueRowDetails = observer(function IssueRowDetails(props: IssueRowDetails
return (
<>
{/* Single sticky column containing both identifier and workitem */}
<td
id={`issue-${issueId}`}
ref={cellRef}
tabIndex={0}
className="relative md:sticky left-0 z-10 group/list-block bg-custom-background-100 min-w-60 max-w-[30vw]"
className="relative md:sticky left-0 z-10 group/list-block bg-custom-background-100"
>
<ControlLink
href={workItemLink}
@ -278,7 +274,29 @@ const IssueRowDetails = observer(function IssueRowDetails(props: IssueRowDetails
}
)}
>
<div className="flex items-center gap-0.5 min-w-min py-2">
{/* Identifier section - conditionally rendered */}
{displayProperties?.key && (
<div className="flex-shrink-0 flex items-center h-full min-w-24">
<div className="relative flex cursor-pointer items-center text-xs hover:text-custom-text-100">
{issueDetail.project_id && (
<IssueIdentifier
issueId={issueDetail.id}
projectId={issueDetail.project_id}
textContainerClassName="text-sm md:text-xs text-custom-text-300"
displayProperties={displayProperties}
/>
)}
</div>
</div>
)}
{/* Workitem section */}
<div
className={cn("flex items-center gap-0.5 py-2 flex-grow", {
"min-w-[360px]": !displayProperties?.key,
"min-w-60": displayProperties?.key,
})}
>
{/* select checkbox */}
{projectId && canSelectIssues && (
<Tooltip
@ -311,21 +329,6 @@ const IssueRowDetails = observer(function IssueRowDetails(props: IssueRowDetails
{/* sub issues indentation */}
{nestingLevel !== 0 && <div style={{ width: subIssueIndentation }} />}
{(displayProperties?.key || displayProperties?.issue_type) && (
<div className="relative flex cursor-pointer items-center text-center text-xs hover:text-custom-text-100">
<p className={`flex font-medium leading-7`} style={{ minWidth: `${keyMinWidth}px` }}>
{issueDetail.project_id && (
<IssueIdentifier
issueId={issueDetail.id}
projectId={issueDetail.project_id}
textContainerClassName="text-sm md:text-xs text-custom-text-300"
displayProperties={displayProperties}
/>
)}
</p>
</div>
)}
{/* sub-issues chevron */}
<div className="grid place-items-center size-4">
{subIssuesCount > 0 && !isEpic && (
@ -343,31 +346,31 @@ const IssueRowDetails = observer(function IssueRowDetails(props: IssueRowDetails
</button>
)}
</div>
</div>
<div className="flex items-center gap-2 justify-between h-full w-full truncate my-auto">
<div className="w-full line-clamp-1 text-sm text-custom-text-100">
<div className="w-full overflow-hidden">
<Tooltip tooltipContent={issueDetail.name} isMobile={isMobile}>
<div
className="h-full w-full cursor-pointer truncate pr-4 text-left text-[0.825rem] text-custom-text-100 focus:outline-none"
tabIndex={-1}
>
{issueDetail.name}
</div>
</Tooltip>
<div className="flex items-center gap-2 justify-between h-full w-full truncate my-auto">
<div className="w-full line-clamp-1 text-sm text-custom-text-100">
<div className="w-full overflow-hidden">
<Tooltip tooltipContent={issueDetail.name} isMobile={isMobile}>
<div
className="h-full w-full cursor-pointer truncate pr-4 text-left text-[0.825rem] text-custom-text-100 focus:outline-none"
tabIndex={-1}
>
{issueDetail.name}
</div>
</Tooltip>
</div>
</div>
<div
className={`opacity-0 group-hover:opacity-100 transition-opacity ${isMenuActive ? "!opacity-100" : ""}`}
onClick={(e) => e.stopPropagation()}
>
{quickActions({
issue: issueDetail,
parentRef: cellRef,
customActionButton,
portalElement: portalElement.current,
})}
</div>
</div>
<div
className={`hidden group-hover:block ${isMenuActive ? "!block" : ""}`}
onClick={(e) => e.stopPropagation()}
>
{quickActions({
issue: issueDetail,
parentRef: cellRef,
customActionButton,
portalElement: portalElement.current,
})}
</div>
</div>
</Row>

View file

@ -5,7 +5,6 @@ import { SPREADSHEET_SELECT_GROUP } from "@plane/constants";
// ui
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
// components
import { Row } from "@plane/ui";
import { cn } from "@plane/utils";
import { MultipleSelectGroupAction } from "@/components/core/multiple-select";
// hooks
@ -44,27 +43,31 @@ export const SpreadsheetHeader = observer(function SpreadsheetHeader(props: Prop
return (
<thead className="sticky top-0 left-0 z-[12] border-b-[0.5px] border-custom-border-100">
<tr>
{/* Single header column containing both identifier and workitem */}
<th
className="group/list-header sticky min-w-60 left-0 z-[15] h-11 flex items-center gap-1 bg-custom-background-90 text-sm font-medium before:absolute before:h-full before:right-0 before:border-custom-border-100"
className="group/list-header md:sticky min-w-60 left-0 z-[15] h-11 bg-custom-background-90 text-sm font-medium border-r-[0.5px] border-custom-border-100"
tabIndex={-1}
>
<Row>
{canSelectIssues && (
<div className="flex-shrink-0 flex items-center w-3.5 mr-1 absolute left-1 py-[11px]">
<MultipleSelectGroupAction
className={cn(
"size-3.5 opacity-0 pointer-events-none group-hover/list-header:opacity-100 group-hover/list-header:pointer-events-auto !outline-none",
{
"opacity-100 pointer-events-auto": !isGroupSelectionEmpty,
}
)}
groupID={SPREADSHEET_SELECT_GROUP}
selectionHelpers={selectionHelpers}
/>
</div>
)}
<span className="flex h-full w-full flex-grow items-center py-2.5">{`${isEpic ? "Epics" : "Work items"}`}</span>
</Row>
<div className="flex items-center gap-2 h-full w-full px-page-x">
{/* Workitem header section */}
<div className="flex items-center gap-1 flex-grow h-full py-2.5 min-w-80">
{canSelectIssues && (
<div className="flex-shrink-0 flex items-center w-3.5 mr-1">
<MultipleSelectGroupAction
className={cn(
"size-3.5 opacity-0 pointer-events-none group-hover/list-header:opacity-100 group-hover/list-header:pointer-events-auto !outline-none",
{
"opacity-100 pointer-events-auto": !isGroupSelectionEmpty,
}
)}
groupID={SPREADSHEET_SELECT_GROUP}
selectionHelpers={selectionHelpers}
/>
</div>
)}
<span className="text-sm font-medium">{`${isEpic ? "Epics" : "Work items"}`}</span>
</div>
</div>
</th>
{spreadsheetColumnsList.map((property) => (

View file

@ -39,7 +39,7 @@ function ProjectCommonAttributes(props: Props) {
return;
}
if (e.target.value === "") setValue("identifier", "");
else setValue("identifier", projectIdentifierSanitizer(e.target.value).substring(0, 5));
else setValue("identifier", projectIdentifierSanitizer(e.target.value).substring(0, 10));
onChange(e);
handleFormOnChange?.();
};
@ -91,11 +91,11 @@ function ProjectCommonAttributes(props: Props) {
/^[ÇŞĞIİÖÜA-Z0-9]+$/.test(value.toUpperCase()) || t("only_alphanumeric_non_latin_characters_allowed"),
minLength: {
value: 1,
message: t("project_id_must_be_at_least_1_character"),
message: t("project_id_min_char"),
},
maxLength: {
value: 5,
message: t("project_id_must_be_at_most_5_characters"),
value: 10,
message: t("project_id_max_char"),
},
}}
render={({ field: { value, onChange } }) => (

View file

@ -185,7 +185,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
if (project.identifier !== formData.identifier)
await projectService
.checkProjectIdentifierAvailability(workspaceSlug as string, payload.identifier ?? "")
.checkProjectIdentifierAvailability(workspaceSlug, payload.identifier ?? "")
.then(async (res) => {
if (res.exists) setError("identifier", { message: t("common.identifier_already_exists") });
else await handleUpdateChange(payload);
@ -338,7 +338,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
message: t("project_id_min_char"),
},
maxLength: {
value: 5,
value: 10,
message: t("project_id_max_char"),
},
}}
@ -359,7 +359,7 @@ export function ProjectDetailsForm(props: IProjectDetailsForm) {
/>
<Tooltip
isMobile={isMobile}
tooltipContent="Helps you identify work items in the project uniquely. Max 5 characters."
tooltipContent={t("project_id_tooltip_content")}
className="text-sm"
position="right-start"
>