-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProjectSwitchMenu.tsx
More file actions
128 lines (121 loc) · 5.36 KB
/
Copy pathProjectSwitchMenu.tsx
File metadata and controls
128 lines (121 loc) · 5.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { startTransition } from "react";
import { ChevronDown, FolderOpen, House, Monitor, Server } from "lucide-react";
import { Dropdown, Label } from "@heroui/react";
import { useShallow } from "zustand/shallow";
import type { Project } from "@/shared/contracts";
import { HOME_PROJECT_NAME, isHomeProject, isHomeProjectId } from "@/shared/homeScope";
import { makeDraftPaneId } from "@/shared/paneId";
import { useAppStore } from "@/renderer/state/appStore";
import { TuxIcon } from "@/renderer/components/common/TuxIcon";
function LocationIcon(props: { kind: Project["location"]["kind"]; className?: string }) {
if (props.kind === "wsl") {
return (
<span className={`${props.className ?? "size-3.5"} relative shrink-0 text-muted`}>
<TuxIcon className="absolute left-1/2 top-1/2 h-3.5 w-6 -translate-x-1/2 -translate-y-1/2" />
</span>
);
}
const className = `${props.className ?? "size-4"} shrink-0 text-muted`;
if (props.kind === "windows") {
return <Monitor className={className} />;
}
if (props.kind === "ssh") {
return <Server className={className} />;
}
return <FolderOpen className={className} />;
}
export function ProjectSwitchMenu(props: {
currentProjectId: string;
variant: "hero" | "compact";
/** When provided, switching replaces this pane id instead of changing the top-level draft view. */
paneId?: string;
}) {
const { currentProjectId, variant, paneId } = props;
// Show every selectable project. Home is intentionally stored with
// `disabled: true` as an internal marker (it's not a user-disabled
// project), so we let it through the filter and only exclude
// user-disabled regular projects.
const projects = useAppStore(
useShallow((state) => state.projects.filter((p) => isHomeProject(p) || !p.disabled)),
);
const openDraft = useAppStore((state) => state.openDraft);
const replacePaneId = useAppStore((state) => state.replacePaneId);
const current = projects.find((p) => p.id === currentProjectId);
const isHomeCurrent = isHomeProjectId(currentProjectId);
const label = isHomeCurrent ? HOME_PROJECT_NAME : (current?.name ?? "Select project");
const triggerIcon = isHomeCurrent ? (
<House className="size-3.5 shrink-0 text-muted" />
) : current ? (
<LocationIcon kind={current.location.kind} className="size-3.5" />
) : null;
const isDisabled = projects.length <= 1;
function handleSelect(nextProjectId: string) {
if (nextProjectId === currentProjectId) return;
startTransition(() => {
if (paneId) {
replacePaneId(paneId, makeDraftPaneId(nextProjectId));
} else {
openDraft(nextProjectId);
}
});
}
const menu = (
<Dropdown.Menu
aria-label="Switch project"
selectionMode="single"
selectedKeys={[currentProjectId]}
onAction={(key) => handleSelect(String(key))}
className="lightcode-menu min-w-56"
>
{projects.map((project) => {
const isHome = isHomeProject(project);
const itemLabel = isHome ? HOME_PROJECT_NAME : project.name;
return (
<Dropdown.Item key={project.id} id={project.id} textValue={itemLabel}>
{isHome ? (
<House className="size-4 shrink-0 text-muted" />
) : (
<LocationIcon kind={project.location.kind} />
)}
<Label>{itemLabel}</Label>
</Dropdown.Item>
);
})}
</Dropdown.Menu>
);
if (variant === "hero") {
return (
<Dropdown>
<Dropdown.Trigger
aria-label="Switch project"
isDisabled={isDisabled}
className="group mx-auto inline-flex max-w-full items-center gap-1.5 rounded border border-transparent px-2 py-0.5 outline-none transition-colors hover:border-border/60 hover:bg-[var(--row-hover)] focus-visible:border-border focus-visible:bg-[var(--row-hover)] disabled:cursor-default disabled:hover:border-transparent disabled:hover:bg-transparent"
>
<span className="min-w-0 truncate pb-[0.08em] leading-snug font-medium tracking-normal text-transparent [background-image:linear-gradient(135deg,var(--muted)_0%,color-mix(in_oklab,var(--accent)_30%,var(--muted))_100%)] [background-size:100%_100%] bg-clip-text font-mono">
{label}
</span>
{!isDisabled ? (
<ChevronDown className="size-3 shrink-0 text-muted/60 opacity-60 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100" />
) : null}
</Dropdown.Trigger>
<Dropdown.Popover placement="bottom">{menu}</Dropdown.Popover>
</Dropdown>
);
}
return (
<Dropdown>
<Dropdown.Trigger
aria-label="Switch project"
isDisabled={isDisabled}
className="group inline-flex min-w-0 max-w-full items-center gap-1 rounded px-1 py-0.5 text-sm leading-tight text-muted/60 outline-none transition-colors hover:bg-[var(--row-hover)] hover:text-foreground focus-visible:bg-[var(--row-hover)] disabled:cursor-default disabled:hover:bg-transparent disabled:hover:text-muted/60"
>
{triggerIcon}
<span className="min-w-0 truncate">{label}</span>
{!isDisabled ? (
<ChevronDown className="size-3 shrink-0 opacity-60 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100" />
) : null}
</Dropdown.Trigger>
<Dropdown.Popover placement="bottom end">{menu}</Dropdown.Popover>
</Dropdown>
);
}