mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
desktop: test layout — single workspace box for project detail
Restructure the project detail view so the repository / source / branch selection row sits at the top of one panel box, with the tab strip and the active panel inside it. The readme header rows move up: source controls into the selection row, the last-changed timestamp into the tab strip. The files panel drops its duplicated source controls and keeps only its path breadcrumb. All selection dropdowns (repository, source, branch) now share one trigger style. Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
@@ -843,36 +843,6 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-row overflow-hidden">
|
||||
<div className="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<ProjectDetailChrome
|
||||
actions={
|
||||
<>
|
||||
<ProjectRepositoryManagement
|
||||
identityPubkey={identityQuery.data?.pubkey}
|
||||
onChange={handleRepositoryChange}
|
||||
project={project}
|
||||
projects={projectsQuery.data ?? []}
|
||||
repository={repository}
|
||||
/>
|
||||
{repoRemote.webUrl &&
|
||||
(repoRemote.host.kind !== "external" ||
|
||||
repoSource === "local") ? (
|
||||
<Button
|
||||
asChild
|
||||
aria-label="Open project web page"
|
||||
className="h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<a
|
||||
href={repoRemote.webUrl}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
activeTabCrumb={activeTabCrumb}
|
||||
activeWorkItemCrumb={activeWorkItemCrumb}
|
||||
chromeRef={projectDetailHeaderChromeRef}
|
||||
@@ -932,6 +902,36 @@ export function ProjectDetailScreen(props: ProjectDetailScreenProps) {
|
||||
onSelectedTabChange={setActiveTab}
|
||||
profiles={profiles}
|
||||
project={repository}
|
||||
repositoryControls={
|
||||
<>
|
||||
<ProjectRepositoryManagement
|
||||
identityPubkey={identityQuery.data?.pubkey}
|
||||
onChange={handleRepositoryChange}
|
||||
project={project}
|
||||
projects={projectsQuery.data ?? []}
|
||||
repository={repository}
|
||||
/>
|
||||
{repoRemote.webUrl &&
|
||||
(repoRemote.host.kind !== "external" ||
|
||||
repoSource === "local") ? (
|
||||
<Button
|
||||
asChild
|
||||
aria-label="Open project web page"
|
||||
className="h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground"
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<a
|
||||
href={repoRemote.webUrl}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
projectId={project.id}
|
||||
repoDiff={displayedRepoDiff}
|
||||
repoDiffError={displayedRepoDiffError}
|
||||
|
||||
@@ -36,6 +36,8 @@ type ProjectOverviewPanelProps = {
|
||||
externalUrl?: string | null;
|
||||
files: ProjectRepoFile[];
|
||||
gitDataState: GitDataState;
|
||||
/** Hide the readme header rows when the workspace renders them itself. */
|
||||
hideReadmeHeader?: boolean;
|
||||
project: Project;
|
||||
onViewContributors: () => void;
|
||||
profiles?: UserProfileLookup;
|
||||
@@ -149,6 +151,7 @@ export function ProjectOverviewPanel({
|
||||
externalUrl,
|
||||
files,
|
||||
gitDataState,
|
||||
hideReadmeHeader,
|
||||
onViewContributors,
|
||||
project,
|
||||
profiles,
|
||||
@@ -182,6 +185,7 @@ export function ProjectOverviewPanel({
|
||||
externalUrl={externalUrl}
|
||||
file={readmeFile}
|
||||
gitDataState={gitDataState}
|
||||
hideHeader={hideReadmeHeader}
|
||||
sourceControls={sourceControls}
|
||||
unavailableReason={unavailableReason}
|
||||
/>
|
||||
|
||||
@@ -140,6 +140,7 @@ export function ReadmePanel({
|
||||
gitDataState,
|
||||
externalHost,
|
||||
externalUrl,
|
||||
hideHeader,
|
||||
sourceControls,
|
||||
unavailableReason,
|
||||
}: {
|
||||
@@ -149,13 +150,18 @@ export function ReadmePanel({
|
||||
gitDataState: "checking" | "available" | "empty" | "unavailable";
|
||||
externalHost?: string;
|
||||
externalUrl?: string | null;
|
||||
/**
|
||||
* Skip the header rows entirely — the workspace layout renders the source
|
||||
* controls and last-changed timestamp itself.
|
||||
*/
|
||||
hideHeader?: boolean;
|
||||
unavailableReason?: ProjectRepoUnavailableReason;
|
||||
/** Branch picker + remote/local toggle rendered in the panel header. */
|
||||
sourceControls?: RepoSourceHeaderControls;
|
||||
}) {
|
||||
// Two header rows, mirroring the files panel: controls on top, then the
|
||||
// file identity row.
|
||||
const header = (
|
||||
const header = hideHeader ? null : (
|
||||
<>
|
||||
{sourceControls ? (
|
||||
<div className="flex min-h-14 min-w-0 items-center gap-1 border-border/50 border-b px-3 py-3">
|
||||
@@ -163,7 +169,6 @@ export function ReadmePanel({
|
||||
<RepositoryBranchDropdown
|
||||
branch={sourceControls.branch}
|
||||
branchOptions={sourceControls.branchOptions}
|
||||
compact
|
||||
createBranchDisabled={sourceControls.createBranchDisabled}
|
||||
createBranchTitle={sourceControls.createBranchTitle}
|
||||
deleteBranchDisabled={sourceControls.deleteBranchDisabled}
|
||||
|
||||
@@ -118,7 +118,7 @@ export function ProjectRepositoryManagement({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className="h-8 shrink-0 gap-1.5"
|
||||
className="h-7 shrink-0 gap-1.5 rounded-md"
|
||||
disabled={repairMutation.isPending}
|
||||
size="sm"
|
||||
type="button"
|
||||
|
||||
@@ -710,7 +710,6 @@ export function RepositoryFilesPanel({
|
||||
<RepositoryBranchDropdown
|
||||
branch={sourceControls.branch}
|
||||
branchOptions={sourceControls.branchOptions}
|
||||
compact
|
||||
createBranchDisabled={sourceControls.createBranchDisabled}
|
||||
createBranchTitle={sourceControls.createBranchTitle}
|
||||
deleteBranchDisabled={sourceControls.deleteBranchDisabled}
|
||||
@@ -752,7 +751,6 @@ export function RepositoryFilesPanel({
|
||||
<RepositoryBranchDropdown
|
||||
branch={sourceControls.branch}
|
||||
branchOptions={sourceControls.branchOptions}
|
||||
compact
|
||||
createBranchDisabled={sourceControls.createBranchDisabled}
|
||||
createBranchTitle={sourceControls.createBranchTitle}
|
||||
deleteBranchDisabled={sourceControls.deleteBranchDisabled}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
FolderGit2,
|
||||
FolderPlus,
|
||||
GitBranch,
|
||||
Link,
|
||||
Plus,
|
||||
} from "lucide-react";
|
||||
|
||||
import type { Project, Repository } from "@/features/projects/hooks";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/ui/dropdown-menu";
|
||||
import { PROJECT_PICKER_TRIGGER_CLASS } from "./projectPanelStyles";
|
||||
|
||||
export function ProjectRepositoryPicker({
|
||||
onAttach,
|
||||
@@ -32,7 +34,7 @@ export function ProjectRepositoryPicker({
|
||||
}) {
|
||||
const repositoryLabel = (
|
||||
<>
|
||||
<GitBranch className="h-3.5 w-3.5" />
|
||||
<FolderGit2 className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{repository.name}</span>
|
||||
</>
|
||||
);
|
||||
@@ -42,7 +44,10 @@ export function ProjectRepositoryPicker({
|
||||
<div className="flex items-center gap-1.5">
|
||||
{project.repositoryAddresses.length === 1 ? (
|
||||
<div
|
||||
className="flex h-8 max-w-64 shrink-0 items-center gap-1.5 rounded-md border border-input bg-background px-3 text-sm font-medium"
|
||||
className={cn(
|
||||
"flex max-w-64 items-center rounded-md border border-input bg-background",
|
||||
PROJECT_PICKER_TRIGGER_CLASS,
|
||||
)}
|
||||
data-testid="project-repository-picker"
|
||||
>
|
||||
{repositoryLabel}
|
||||
@@ -52,13 +57,13 @@ export function ProjectRepositoryPicker({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-label="Select repository"
|
||||
className="h-8 max-w-64 shrink-0 gap-1.5"
|
||||
className={cn("max-w-64", PROJECT_PICKER_TRIGGER_CLASS)}
|
||||
data-testid="project-repository-picker"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
{repositoryLabel}
|
||||
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="min-w-56">
|
||||
@@ -98,7 +103,7 @@ export function ProjectRepositoryPicker({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
aria-label="Add repository"
|
||||
className="h-8 w-8 shrink-0"
|
||||
className="h-7 w-7 shrink-0 rounded-md"
|
||||
data-testid="add-project-repository"
|
||||
size="icon"
|
||||
type="button"
|
||||
|
||||
@@ -26,7 +26,10 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/ui/dropdown-menu";
|
||||
import { GitHubMark } from "./GitHubMark";
|
||||
import { PROJECT_PANEL_ACTION_BUTTON_CLASS } from "./projectPanelStyles";
|
||||
import {
|
||||
PROJECT_PANEL_ACTION_BUTTON_CLASS,
|
||||
PROJECT_PICKER_TRIGGER_CLASS,
|
||||
} from "./projectPanelStyles";
|
||||
|
||||
/** Branch picker shared by the readme and files panel headers. */
|
||||
export function RepositoryBranchDropdown({
|
||||
@@ -34,7 +37,6 @@ export function RepositoryBranchDropdown({
|
||||
branchOptions,
|
||||
selectedTag,
|
||||
tagOptions = [],
|
||||
compact,
|
||||
createBranchDisabled,
|
||||
createBranchTitle,
|
||||
deleteBranchDisabled,
|
||||
@@ -48,8 +50,6 @@ export function RepositoryBranchDropdown({
|
||||
branchOptions: string[];
|
||||
selectedTag?: string | null;
|
||||
tagOptions?: Array<{ name: string; commit: string }>;
|
||||
/** Smaller trigger for inline headers. */
|
||||
compact?: boolean;
|
||||
createBranchDisabled?: boolean;
|
||||
createBranchTitle?: string;
|
||||
deleteBranchDisabled?: boolean;
|
||||
@@ -74,17 +74,13 @@ export function RepositoryBranchDropdown({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className={
|
||||
compact
|
||||
? "h-7 max-w-full gap-1.5 rounded-md px-3 font-mono text-sm font-medium hover:border-input"
|
||||
: "h-6 max-w-full gap-1.5 px-2 font-mono text-sm font-semibold hover:border-input"
|
||||
}
|
||||
className={PROJECT_PICKER_TRIGGER_CLASS}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
<RefIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{selectedTag ?? branch}</span>
|
||||
<span className="truncate font-mono">{selectedTag ?? branch}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -229,7 +225,7 @@ export function RepoSourceDropdown({
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className="h-7 max-w-full shrink-0 gap-1.5 rounded-md px-3 text-sm font-medium hover:border-input"
|
||||
className={PROJECT_PICKER_TRIGGER_CLASS}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
@@ -29,11 +29,20 @@ import {
|
||||
} from "@/features/projects/lib/projectRepoAvailability";
|
||||
import { useMemberChannelIds } from "@/features/projects/useRepositoryAccess";
|
||||
import type { UserProfileLookup } from "@/features/profile/lib/identity";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Tabs, TabsContent } from "@/shared/ui/tabs";
|
||||
import { findReadmeFile } from "./ProjectReadmePanel";
|
||||
import { RepositoryFilesPanel } from "./ProjectRepositoryPanel";
|
||||
import type { RepoSourceHeaderControls } from "./ProjectRepositorySource";
|
||||
import {
|
||||
formatLastChangedAt,
|
||||
RepositoryFilesPanel,
|
||||
} from "./ProjectRepositoryPanel";
|
||||
import {
|
||||
type RepoSourceHeaderControls,
|
||||
RepoSourceDropdown,
|
||||
RepoSyncActionButton,
|
||||
RepositoryBranchDropdown,
|
||||
} from "./ProjectRepositorySource";
|
||||
import { ProjectCommitDetailPanel } from "./ProjectCommitDetailPanel";
|
||||
import { ActivityPanel, ContributorsPanel } from "./ProjectDetailFeedPanels";
|
||||
import { ProjectIssuesPanel } from "./ProjectIssuesPanel";
|
||||
@@ -154,6 +163,7 @@ export function WorkspaceTabs({
|
||||
repoContributors,
|
||||
repoSource,
|
||||
repoHost,
|
||||
repositoryControls,
|
||||
sourceControls,
|
||||
terminalTitle,
|
||||
viewerGitIdentity,
|
||||
@@ -193,7 +203,9 @@ export function WorkspaceTabs({
|
||||
repoContributors: ProjectRepoContributor[];
|
||||
repoSource: "remote" | "local";
|
||||
repoHost: ProjectRepoHost;
|
||||
/** Branch picker + remote/local toggle for the Code tab header. */
|
||||
/** Repository picker (and related actions) for the selection header row. */
|
||||
repositoryControls?: React.ReactNode;
|
||||
/** Branch picker + remote/local toggle for the selection header row. */
|
||||
sourceControls?: RepoSourceHeaderControls;
|
||||
terminalTitle?: string;
|
||||
viewerGitIdentity?: ViewerGitIdentity | null;
|
||||
@@ -326,235 +338,288 @@ export function WorkspaceTabs({
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
className="space-y-3"
|
||||
className="min-w-0"
|
||||
onValueChange={handleTabChange}
|
||||
value={selectedTab}
|
||||
>
|
||||
{repositoryLoaded ? (
|
||||
<div className="flex h-10 min-w-0 items-center gap-1">
|
||||
<ProjectTabsList prsActive={isPullRequestSelected} />
|
||||
{onOpenTerminal ? (
|
||||
<Button
|
||||
aria-label="Open terminal"
|
||||
className="h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => onOpenTerminal()}
|
||||
size="icon"
|
||||
title={terminalTitle ?? "Open terminal"}
|
||||
variant="ghost"
|
||||
>
|
||||
<SquareTerminal className="h-[1.125rem] w-[1.125rem]" />
|
||||
</Button>
|
||||
) : null}
|
||||
{updatePullRequestAction ? (
|
||||
<Button
|
||||
className="h-8 shrink-0 gap-1.5"
|
||||
disabled={updatePullRequestAction.pending}
|
||||
onClick={updatePullRequestAction.onUpdate}
|
||||
size="sm"
|
||||
title="Publish the pushed commit to this pull request"
|
||||
variant="outline"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
{updatePullRequestAction.pending ? "Updating…" : "Update PR"}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{selectedPullRequest ? (
|
||||
<div className={PROJECT_DETAIL_PANEL_CLASS} data-project-detail-panel>
|
||||
{/* Two full-height columns: the meta rail runs all the way to the
|
||||
{/* Single workspace box: selection header (repository / source /
|
||||
branch), then the tab strip, then the active panel. Inner panels
|
||||
keep their own PROJECT_DETAIL_PANEL_CLASS for standalone use, so
|
||||
their border + radius are neutralized here via the data attribute
|
||||
they all carry. */}
|
||||
<div
|
||||
className={cn(
|
||||
PROJECT_DETAIL_PANEL_CLASS,
|
||||
"[&_[data-project-detail-panel]]:rounded-none [&_[data-project-detail-panel]]:border-0",
|
||||
)}
|
||||
data-project-detail-panel
|
||||
>
|
||||
{repositoryControls || sourceControls ? (
|
||||
<div className="flex min-h-12 min-w-0 flex-wrap items-center gap-1.5 border-border/50 border-b px-3 py-2">
|
||||
{repositoryControls}
|
||||
{sourceControls ? (
|
||||
<>
|
||||
<RepoSourceDropdown controls={sourceControls} />
|
||||
<RepositoryBranchDropdown
|
||||
branch={sourceControls.branch}
|
||||
branchOptions={sourceControls.branchOptions}
|
||||
createBranchDisabled={sourceControls.createBranchDisabled}
|
||||
createBranchTitle={sourceControls.createBranchTitle}
|
||||
deleteBranchDisabled={sourceControls.deleteBranchDisabled}
|
||||
deleteBranchTitle={sourceControls.deleteBranchTitle}
|
||||
onBranchChange={sourceControls.onBranchChange}
|
||||
onCreateBranch={sourceControls.onCreateBranch}
|
||||
onDeleteBranch={sourceControls.onDeleteBranch}
|
||||
onTagChange={sourceControls.onTagChange}
|
||||
selectedTag={sourceControls.selectedTag}
|
||||
tagOptions={sourceControls.tagOptions}
|
||||
/>
|
||||
<div className="ml-auto flex shrink-0 items-center">
|
||||
<RepoSyncActionButton controls={sourceControls} />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{repositoryLoaded ? (
|
||||
<div className="flex h-10 min-w-0 items-center gap-1 border-border/50 border-b px-2">
|
||||
<ProjectTabsList prsActive={isPullRequestSelected} />
|
||||
{onOpenTerminal ? (
|
||||
<Button
|
||||
aria-label="Open terminal"
|
||||
className="h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => onOpenTerminal()}
|
||||
size="icon"
|
||||
title={terminalTitle ?? "Open terminal"}
|
||||
variant="ghost"
|
||||
>
|
||||
<SquareTerminal className="h-[1.125rem] w-[1.125rem]" />
|
||||
</Button>
|
||||
) : null}
|
||||
{updatePullRequestAction ? (
|
||||
<Button
|
||||
className="h-8 shrink-0 gap-1.5"
|
||||
disabled={updatePullRequestAction.pending}
|
||||
onClick={updatePullRequestAction.onUpdate}
|
||||
size="sm"
|
||||
title="Publish the pushed commit to this pull request"
|
||||
variant="outline"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
{updatePullRequestAction.pending ? "Updating…" : "Update PR"}
|
||||
</Button>
|
||||
) : null}
|
||||
{selectedTab === "overview" && readmeFile ? (
|
||||
<span className="ml-auto hidden shrink-0 pr-2 text-2xs text-muted-foreground sm:block">
|
||||
Last changed {formatLastChangedAt(readmeFile.lastChangedAt)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{selectedPullRequest ? (
|
||||
<div className={PROJECT_DETAIL_PANEL_CLASS} data-project-detail-panel>
|
||||
{/* Two full-height columns: the meta rail runs all the way to the
|
||||
top of the card, alongside the header and tabs. */}
|
||||
<div className="grid xl:grid-cols-[minmax(0,1fr)_18rem]">
|
||||
<div className="min-w-0">
|
||||
<PullRequestDetailHeader
|
||||
profiles={profiles}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
<div className="border-b border-border/60 px-4">
|
||||
<PullRequestTabsList
|
||||
filesCount={repoDiff?.files.length ?? files.length}
|
||||
<div className="grid xl:grid-cols-[minmax(0,1fr)_18rem]">
|
||||
<div className="min-w-0">
|
||||
<PullRequestDetailHeader
|
||||
profiles={profiles}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
</div>
|
||||
{(["conversation", "commits", "checks"] as const).map((mode) => (
|
||||
<TabsContent className="m-0" key={mode} value={`pr-${mode}`}>
|
||||
<PullRequestsPanel
|
||||
error={pullRequestsError}
|
||||
isLoading={pullRequestsLoading}
|
||||
mode={mode}
|
||||
onOpenInlineComment={handleOpenPullRequestComment}
|
||||
onOpenCommit={onSelectedCommitHashChange}
|
||||
onOpenTerminal={onOpenMergeRecoveryTerminal}
|
||||
onSelectedPullRequestIdChange={
|
||||
onSelectedPullRequestIdChange
|
||||
<div className="border-b border-border/60 px-4">
|
||||
<PullRequestTabsList
|
||||
filesCount={repoDiff?.files.length ?? files.length}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
</div>
|
||||
{(["conversation", "commits", "checks"] as const).map(
|
||||
(mode) => (
|
||||
<TabsContent
|
||||
className="m-0"
|
||||
key={mode}
|
||||
value={`pr-${mode}`}
|
||||
>
|
||||
<PullRequestsPanel
|
||||
error={pullRequestsError}
|
||||
isLoading={pullRequestsLoading}
|
||||
mode={mode}
|
||||
onOpenInlineComment={handleOpenPullRequestComment}
|
||||
onOpenCommit={onSelectedCommitHashChange}
|
||||
onOpenTerminal={onOpenMergeRecoveryTerminal}
|
||||
onSelectedPullRequestIdChange={
|
||||
onSelectedPullRequestIdChange
|
||||
}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequests={pullRequests}
|
||||
selectedPullRequestId={selectedPullRequestId}
|
||||
/>
|
||||
</TabsContent>
|
||||
),
|
||||
)}
|
||||
<TabsContent className="m-0" value="pr-files">
|
||||
<ProjectPullRequestFilesChangedPanel
|
||||
diff={repoDiff}
|
||||
error={repoDiffError}
|
||||
focusedAnchor={
|
||||
pullRequestCommentTarget?.pullRequestId ===
|
||||
selectedPullRequestId
|
||||
? pullRequestCommentTarget.anchor
|
||||
: null
|
||||
}
|
||||
isLoading={repoDiffLoading}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequests={pullRequests}
|
||||
selectedPullRequestId={selectedPullRequestId}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
</TabsContent>
|
||||
))}
|
||||
<TabsContent className="m-0" value="pr-files">
|
||||
<ProjectPullRequestFilesChangedPanel
|
||||
diff={repoDiff}
|
||||
error={repoDiffError}
|
||||
focusedAnchor={
|
||||
pullRequestCommentTarget?.pullRequestId ===
|
||||
selectedPullRequestId
|
||||
? pullRequestCommentTarget.anchor
|
||||
: null
|
||||
}
|
||||
isLoading={repoDiffLoading}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
<PullRequestMetaRail
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<TabsContent className="m-0" value="overview">
|
||||
<ProjectOverviewPanel
|
||||
accessChannelId={project.channelId}
|
||||
contributors={displayedContributors}
|
||||
externalHost={externalHost}
|
||||
externalUrl={externalHost ? sourceControls?.externalUrl : null}
|
||||
files={files}
|
||||
gitDataState={gitDataState}
|
||||
onViewContributors={() => setSelectedTab("contributors")}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequests={pullRequests}
|
||||
readmeFile={readmeFile}
|
||||
snapshot={displayedSnapshot}
|
||||
sourceControls={sourceControls}
|
||||
unavailableReason={unavailableReason}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent className="m-0" value="activity">
|
||||
{selectedCommitHash ? (
|
||||
<ProjectCommitDetailPanel
|
||||
commit={
|
||||
displayedSnapshot?.commits.find(
|
||||
(commit) => commit.hash === selectedCommitHash,
|
||||
) ?? null
|
||||
}
|
||||
commitAuthorPubkeys={commitAuthorPubkeys}
|
||||
commitHash={selectedCommitHash}
|
||||
viewerGitIdentity={viewerGitIdentity}
|
||||
diff={commitDiff}
|
||||
diffError={commitDiffError}
|
||||
diffLoading={commitDiffLoading}
|
||||
originAgentName={selectedCommitPullRequest?.originAgentName}
|
||||
originChannelId={selectedCommitPullRequest?.channelId}
|
||||
profiles={profiles}
|
||||
/>
|
||||
) : (
|
||||
<ActivityPanel
|
||||
branch={sourceControls?.branch}
|
||||
error={displayedSnapshotError}
|
||||
isLoading={displayedSnapshotLoading}
|
||||
onSelectCommit={(commit) => onSelectedCommitHashChange(commit.hash)}
|
||||
profiles={profiles}
|
||||
pullRequests={pullRequests}
|
||||
repoContributors={displayedContributors}
|
||||
snapshot={displayedSnapshot}
|
||||
viewerGitIdentity={viewerGitIdentity}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
className={`m-0 ${PROJECT_DETAIL_PANEL_CLASS}`}
|
||||
data-project-detail-panel
|
||||
value="prs"
|
||||
>
|
||||
<WorkItemListHeader
|
||||
actionDisabled={
|
||||
!createPullRequestAction ||
|
||||
createPullRequestAction.projects.length === 0
|
||||
}
|
||||
actionLabel="Pull Request"
|
||||
actionTitle="Choose a repository and branches to compare."
|
||||
icon={GitPullRequest}
|
||||
onAction={() => setCreatePullRequestOpen(true)}
|
||||
title="Pull Requests"
|
||||
/>
|
||||
<PullRequestsPanel
|
||||
error={pullRequestsError}
|
||||
isLoading={pullRequestsLoading}
|
||||
onOpenCommit={onSelectedCommitHashChange}
|
||||
onOpenTerminal={onOpenMergeRecoveryTerminal}
|
||||
onSelectedPullRequestIdChange={onSelectedPullRequestIdChange}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequests={pullRequests}
|
||||
selectedPullRequestId={selectedPullRequestId}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
className={`m-0 ${PROJECT_DETAIL_PANEL_CLASS}`}
|
||||
data-project-detail-panel
|
||||
value="issues"
|
||||
>
|
||||
<WorkItemListHeader
|
||||
actionDisabled={createIssueAction.pending}
|
||||
actionLabel="Issues"
|
||||
icon={CircleDot}
|
||||
onAction={() => setCreateIssueOpen(true)}
|
||||
title="Issues"
|
||||
/>
|
||||
<ProjectIssuesPanel
|
||||
onSelectedIssueIdChange={onSelectedIssueIdChange}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
selectedIssueId={selectedIssueId}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent className="m-0" value="files">
|
||||
{repoSource === "local" && !localSnapshot && !localSnapshotLoading ? (
|
||||
<div className="mb-3">
|
||||
<div
|
||||
className={PROJECT_DETAIL_PANEL_MESSAGE_CLASS}
|
||||
data-project-detail-panel
|
||||
>
|
||||
No local checkout found.
|
||||
</div>
|
||||
<PullRequestMetaRail
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequest={selectedPullRequest}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<RepositoryFilesPanel
|
||||
error={displayedSnapshotError}
|
||||
fallbackAuthorPubkey={project.owner}
|
||||
files={files}
|
||||
isLoading={displayedSnapshotLoading}
|
||||
profiles={profiles}
|
||||
snapshot={displayedSnapshot}
|
||||
sourceControls={sourceControls}
|
||||
unavailableMessage={
|
||||
externalHost
|
||||
? `Not mirrored on Buzz. Repository files are hosted on ${externalHost}.`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent className="m-0" value="contributors">
|
||||
<ContributorsPanel
|
||||
profiles={profiles}
|
||||
repoContributors={displayedContributors}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent className="m-0" value="overview">
|
||||
<ProjectOverviewPanel
|
||||
accessChannelId={project.channelId}
|
||||
contributors={displayedContributors}
|
||||
externalHost={externalHost}
|
||||
externalUrl={externalHost ? sourceControls?.externalUrl : null}
|
||||
files={files}
|
||||
gitDataState={gitDataState}
|
||||
hideReadmeHeader
|
||||
onViewContributors={() => setSelectedTab("contributors")}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequests={pullRequests}
|
||||
readmeFile={readmeFile}
|
||||
snapshot={displayedSnapshot}
|
||||
sourceControls={sourceControls}
|
||||
unavailableReason={unavailableReason}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent className="m-0" value="activity">
|
||||
{selectedCommitHash ? (
|
||||
<ProjectCommitDetailPanel
|
||||
commit={
|
||||
displayedSnapshot?.commits.find(
|
||||
(commit) => commit.hash === selectedCommitHash,
|
||||
) ?? null
|
||||
}
|
||||
commitAuthorPubkeys={commitAuthorPubkeys}
|
||||
commitHash={selectedCommitHash}
|
||||
viewerGitIdentity={viewerGitIdentity}
|
||||
diff={commitDiff}
|
||||
diffError={commitDiffError}
|
||||
diffLoading={commitDiffLoading}
|
||||
originAgentName={selectedCommitPullRequest?.originAgentName}
|
||||
originChannelId={selectedCommitPullRequest?.channelId}
|
||||
profiles={profiles}
|
||||
/>
|
||||
) : (
|
||||
<ActivityPanel
|
||||
branch={sourceControls?.branch}
|
||||
error={displayedSnapshotError}
|
||||
isLoading={displayedSnapshotLoading}
|
||||
onSelectCommit={(commit) =>
|
||||
onSelectedCommitHashChange(commit.hash)
|
||||
}
|
||||
profiles={profiles}
|
||||
pullRequests={pullRequests}
|
||||
repoContributors={displayedContributors}
|
||||
snapshot={displayedSnapshot}
|
||||
viewerGitIdentity={viewerGitIdentity}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
className={`m-0 ${PROJECT_DETAIL_PANEL_CLASS}`}
|
||||
data-project-detail-panel
|
||||
value="prs"
|
||||
>
|
||||
<WorkItemListHeader
|
||||
actionDisabled={
|
||||
!createPullRequestAction ||
|
||||
createPullRequestAction.projects.length === 0
|
||||
}
|
||||
actionLabel="Pull Request"
|
||||
actionTitle="Choose a repository and branches to compare."
|
||||
icon={GitPullRequest}
|
||||
onAction={() => setCreatePullRequestOpen(true)}
|
||||
title="Pull Requests"
|
||||
/>
|
||||
<PullRequestsPanel
|
||||
error={pullRequestsError}
|
||||
isLoading={pullRequestsLoading}
|
||||
onOpenCommit={onSelectedCommitHashChange}
|
||||
onOpenTerminal={onOpenMergeRecoveryTerminal}
|
||||
onSelectedPullRequestIdChange={onSelectedPullRequestIdChange}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
pullRequests={pullRequests}
|
||||
selectedPullRequestId={selectedPullRequestId}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
className={`m-0 ${PROJECT_DETAIL_PANEL_CLASS}`}
|
||||
data-project-detail-panel
|
||||
value="issues"
|
||||
>
|
||||
<WorkItemListHeader
|
||||
actionDisabled={createIssueAction.pending}
|
||||
actionLabel="Issues"
|
||||
icon={CircleDot}
|
||||
onAction={() => setCreateIssueOpen(true)}
|
||||
title="Issues"
|
||||
/>
|
||||
<ProjectIssuesPanel
|
||||
onSelectedIssueIdChange={onSelectedIssueIdChange}
|
||||
profiles={profiles}
|
||||
project={project}
|
||||
selectedIssueId={selectedIssueId}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent className="m-0" value="files">
|
||||
{repoSource === "local" && !localSnapshot && !localSnapshotLoading ? (
|
||||
<div className="mb-3">
|
||||
<div
|
||||
className={PROJECT_DETAIL_PANEL_MESSAGE_CLASS}
|
||||
data-project-detail-panel
|
||||
>
|
||||
No local checkout found.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<RepositoryFilesPanel
|
||||
error={displayedSnapshotError}
|
||||
fallbackAuthorPubkey={project.owner}
|
||||
files={files}
|
||||
isLoading={displayedSnapshotLoading}
|
||||
profiles={profiles}
|
||||
snapshot={displayedSnapshot}
|
||||
unavailableMessage={
|
||||
externalHost
|
||||
? `Not mirrored on Buzz. Repository files are hosted on ${externalHost}.`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent className="m-0" value="contributors">
|
||||
<ContributorsPanel
|
||||
profiles={profiles}
|
||||
repoContributors={displayedContributors}
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
{createPullRequestAction && createPullRequestOpen ? (
|
||||
<CreatePullRequestDialog
|
||||
initialProjectId={projectId}
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
export const PROJECT_PANEL_ACTION_BUTTON_CLASS =
|
||||
"h-auto shrink-0 gap-1.5 rounded-full border-transparent bg-muted px-3 py-1.5 text-sm font-medium text-foreground shadow-none hover:bg-muted/80";
|
||||
|
||||
/**
|
||||
* Shared trigger for the selection dropdowns (repository, source, branch) so
|
||||
* they read as one consistent control family in the workspace header.
|
||||
*/
|
||||
export const PROJECT_PICKER_TRIGGER_CLASS =
|
||||
"h-7 max-w-full shrink-0 gap-1.5 rounded-md px-3 text-sm font-medium hover:border-input";
|
||||
|
||||
/** Bordered shell that lets the project page surface show through. */
|
||||
export const PROJECT_DETAIL_PANEL_CLASS =
|
||||
"overflow-hidden rounded-xl border border-border/60 bg-transparent";
|
||||
|
||||
Reference in New Issue
Block a user