mirror of
https://github.com/block/buzz.git
synced 2026-11-02 15:31:27 +00:00
feat(desktop): polish selectable Projects workspaces
Unify grouped selection, contextual actions, agent chat, and local repository controls so Projects workflows stay focused across overview and detail views. Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
@@ -28,6 +28,11 @@ import type {
|
||||
Repository,
|
||||
} from "@/features/projects/hooks";
|
||||
import { projectExternalRefUrl } from "@/features/projects/lib/projectExternalUrl";
|
||||
import {
|
||||
type ProjectSelectionItem,
|
||||
projectSelectionPresentation,
|
||||
} from "@/features/projects/lib/projectSelection";
|
||||
import { useProjectSelection } from "@/features/projects/lib/useProjectSelection";
|
||||
import type { UserProfileLookup } from "@/features/profile/lib/identity";
|
||||
import { RightAuxiliaryPane } from "@/features/channels/ui/RightAuxiliaryPane";
|
||||
import { normalizePubkey } from "@/shared/lib/pubkey";
|
||||
@@ -40,11 +45,15 @@ import {
|
||||
RepositoryBranchDropdown,
|
||||
} from "./ProjectRepositorySource";
|
||||
import { ProjectRepositoryManagement } from "./ProjectRepositoryManagement";
|
||||
import { ProjectWorkItemContextActions } from "./ProjectWorkItemContextActions";
|
||||
import { ProjectWorkItemContextDetails } from "./ProjectWorkItemContextDetails";
|
||||
import { ProjectsSelectionCountMenu } from "./ProjectsSelectionCountMenu";
|
||||
import {
|
||||
projectReviewActivity,
|
||||
projectRightPanelScope,
|
||||
projectTaskActivity,
|
||||
} from "./projectRightPanelContext";
|
||||
import { PROJECT_CONTEXT_ACTION_BUTTON_CLASS } from "./projectContextActionStyles";
|
||||
|
||||
type ProjectRepositoryActionsPanelProps = {
|
||||
activeTab: string;
|
||||
@@ -55,7 +64,9 @@ type ProjectRepositoryActionsPanelProps = {
|
||||
files: ProjectRepoFile[];
|
||||
identityPubkey?: string;
|
||||
issues: ProjectIssue[];
|
||||
onChatWithAgent: (items: ProjectSelectionItem[]) => void;
|
||||
onCreateTask: () => void;
|
||||
onCreatePullRequest?: () => void;
|
||||
onOpenLocalRepository: () => void;
|
||||
onOpenTerminal: () => void;
|
||||
onRepositoryChange: (repositoryId: string) => void;
|
||||
@@ -66,6 +77,8 @@ type ProjectRepositoryActionsPanelProps = {
|
||||
project: Project;
|
||||
projects: Project[];
|
||||
repository: Repository;
|
||||
selectedIssue?: ProjectIssue | null;
|
||||
selectedPullRequest?: ProjectPullRequest | null;
|
||||
snapshot: ProjectRepoSnapshot | null | undefined;
|
||||
sourceControls: RepoSourceHeaderControls;
|
||||
terminalTitle?: string;
|
||||
@@ -82,13 +95,7 @@ function RepositoryPanelSection({
|
||||
title?: string;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={
|
||||
divided
|
||||
? "!mt-2 space-y-0.5 border-border/50 border-t pt-2"
|
||||
: "space-y-0.5"
|
||||
}
|
||||
>
|
||||
<section className={divided ? "!mt-2 space-y-0.5 pt-2" : "space-y-0.5"}>
|
||||
{title ? (
|
||||
<h3 className="flex h-7 min-w-0 items-center truncate text-sm font-normal text-muted-foreground/70">
|
||||
{title}
|
||||
@@ -155,7 +162,7 @@ function RepositoryActionButton({
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
className="-mx-2 h-7 w-[calc(100%+1rem)] justify-start gap-3 rounded-md px-2 text-left text-sm font-normal hover:bg-muted/70 disabled:text-muted-foreground [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground"
|
||||
className={PROJECT_CONTEXT_ACTION_BUTTON_CLASS}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
size="sm"
|
||||
@@ -177,7 +184,9 @@ export function ProjectRepositoryActionsPanel({
|
||||
files,
|
||||
identityPubkey,
|
||||
issues,
|
||||
onChatWithAgent,
|
||||
onCreateTask,
|
||||
onCreatePullRequest,
|
||||
onOpenLocalRepository,
|
||||
onOpenTerminal,
|
||||
onRepositoryChange,
|
||||
@@ -188,18 +197,30 @@ export function ProjectRepositoryActionsPanel({
|
||||
project,
|
||||
projects,
|
||||
repository,
|
||||
selectedIssue,
|
||||
selectedPullRequest,
|
||||
snapshot,
|
||||
sourceControls,
|
||||
terminalTitle,
|
||||
widthPx,
|
||||
}: ProjectRepositoryActionsPanelProps) {
|
||||
const contextPeople = selectedIssue
|
||||
? [selectedIssue.author, ...selectedIssue.assignees]
|
||||
: selectedPullRequest
|
||||
? [
|
||||
selectedPullRequest.author,
|
||||
...selectedPullRequest.reviewers,
|
||||
...selectedPullRequest.approvals.map(({ author }) => author),
|
||||
...selectedPullRequest.changeRequests.map(({ author }) => author),
|
||||
]
|
||||
: [repository.owner, ...repository.contributors];
|
||||
const people = [
|
||||
...new Set(
|
||||
[repository.owner, ...repository.contributors]
|
||||
.filter(Boolean)
|
||||
.map(normalizePubkey),
|
||||
),
|
||||
...new Set(contextPeople.filter(Boolean).map(normalizePubkey)),
|
||||
];
|
||||
const selection = useProjectSelection();
|
||||
const selectionPresentation = projectSelectionPresentation(
|
||||
selection?.items ?? [],
|
||||
);
|
||||
const latestCommit = snapshot?.latestCommit ?? null;
|
||||
const scope = projectRightPanelScope(activeTab);
|
||||
const branchScoped = scope === "branch";
|
||||
@@ -226,6 +247,7 @@ export function ProjectRepositoryActionsPanel({
|
||||
return (
|
||||
<RightAuxiliaryPane
|
||||
canResetWidth={canResetWidth}
|
||||
constrainToAvailableSpace={!detached}
|
||||
detached={detached}
|
||||
onResetWidth={onResetWidth}
|
||||
onResizeStart={onResizeStart}
|
||||
@@ -243,27 +265,54 @@ export function ProjectRepositoryActionsPanel({
|
||||
className={`rounded-2xl ${detached ? "bg-background" : "border border-border/60 bg-muted/30"}`}
|
||||
data-testid="project-context-card"
|
||||
>
|
||||
<div className="flex min-w-0 items-center justify-between gap-2 px-4 pt-3">
|
||||
<h2 className="min-w-0 truncate text-sm font-normal text-muted-foreground/70">
|
||||
{repository.name}
|
||||
</h2>
|
||||
{branchScoped ? (
|
||||
<div
|
||||
className="flex shrink-0 items-center"
|
||||
data-testid="project-repository-management-actions"
|
||||
>
|
||||
<ProjectRepositoryManagement
|
||||
compact
|
||||
identityPubkey={identityPubkey}
|
||||
onChange={onRepositoryChange}
|
||||
project={project}
|
||||
projects={projects}
|
||||
repository={repository}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={`flex min-w-0 items-center justify-between gap-2 px-4 pt-3 ${
|
||||
selectionPresentation ? "pb-3" : ""
|
||||
}`}
|
||||
>
|
||||
{selectionPresentation && selection ? (
|
||||
<ProjectsSelectionCountMenu
|
||||
onChatWithAgent={onChatWithAgent}
|
||||
onCreatePullRequest={onCreatePullRequest}
|
||||
presentation={selectionPresentation}
|
||||
selectionItems={selection.items}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="min-w-0 truncate text-sm font-normal text-muted-foreground/70">
|
||||
{selectedIssue?.title ??
|
||||
selectedPullRequest?.title ??
|
||||
repository.name}
|
||||
</h2>
|
||||
{branchScoped ? (
|
||||
<div
|
||||
className="flex shrink-0 items-center"
|
||||
data-testid="project-repository-management-actions"
|
||||
>
|
||||
<ProjectRepositoryManagement
|
||||
compact
|
||||
identityPubkey={identityPubkey}
|
||||
onChange={onRepositoryChange}
|
||||
project={project}
|
||||
projects={projects}
|
||||
repository={repository}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-0.5 px-4 pb-3 pt-2">
|
||||
<div
|
||||
className={`space-y-0.5 px-4 pb-3 pt-2 ${
|
||||
selectionPresentation ? "hidden" : ""
|
||||
}`}
|
||||
>
|
||||
<ProjectWorkItemContextActions
|
||||
issue={selectedIssue}
|
||||
profiles={profiles}
|
||||
pullRequest={selectedPullRequest}
|
||||
repository={repository}
|
||||
/>
|
||||
{branchScoped ? (
|
||||
<RepositoryPanelSection>
|
||||
<div className="grid gap-0.5 [&_button]:-mx-2 [&_button]:h-7 [&_button]:w-[calc(100%+1rem)] [&_button]:max-w-none [&_button]:justify-start [&_button]:gap-3 [&_button]:rounded-md [&_button]:border-0 [&_button]:bg-transparent [&_button]:px-2 [&_button]:text-left [&_button]:text-sm [&_button]:font-normal [&_button]:shadow-none [&_button]:hover:bg-muted/70 [&_button>svg:last-child]:ml-auto">
|
||||
@@ -284,7 +333,7 @@ export function ProjectRepositoryActionsPanel({
|
||||
/>
|
||||
</div>
|
||||
</RepositoryPanelSection>
|
||||
) : activeTab === "issues" ? (
|
||||
) : activeTab === "issues" && !selectionPresentation ? (
|
||||
<RepositoryPanelSection>
|
||||
<RepositoryActionButton
|
||||
disabled={createIssuePending}
|
||||
@@ -446,21 +495,37 @@ export function ProjectRepositoryActionsPanel({
|
||||
<>
|
||||
<RepositoryPanelSection
|
||||
divided={activeTab === "issues"}
|
||||
title={activeTab === "issues" ? "Details" : undefined}
|
||||
title={
|
||||
selectedIssue || selectedPullRequest
|
||||
? "People"
|
||||
: activeTab === "issues"
|
||||
? "Details"
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<RepositoryPeople people={people} profiles={profiles} />
|
||||
</RepositoryPanelSection>
|
||||
<RepositoryPanelSection
|
||||
title={
|
||||
activeTab === "issues"
|
||||
? undefined
|
||||
: activeTab === "prs"
|
||||
? "Review activity"
|
||||
: "Repository activity"
|
||||
selectedIssue
|
||||
? "Task details"
|
||||
: selectedPullRequest
|
||||
? "Review details"
|
||||
: activeTab === "issues"
|
||||
? undefined
|
||||
: activeTab === "prs"
|
||||
? "Review activity"
|
||||
: "Repository activity"
|
||||
}
|
||||
>
|
||||
<dl className="text-sm [&>div]:h-7 [&_dd]:ml-auto [&_dd]:tabular-nums [&_dt]:gap-3 [&_dt]:text-foreground [&_dt_svg]:h-4 [&_dt_svg]:w-4 [&_dt_svg]:shrink-0 [&_dt_svg]:text-muted-foreground">
|
||||
{activeTab !== "prs" ? (
|
||||
<ProjectWorkItemContextDetails
|
||||
issue={selectedIssue}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
{!selectedIssue &&
|
||||
!selectedPullRequest &&
|
||||
activeTab !== "prs" ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<dt className="flex items-center gap-3 text-muted-foreground">
|
||||
@@ -497,7 +562,9 @@ export function ProjectRepositoryActionsPanel({
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
{activeTab !== "issues" ? (
|
||||
{!selectedIssue &&
|
||||
!selectedPullRequest &&
|
||||
activeTab !== "issues" ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<dt className="flex items-center gap-3 text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user