Repository navigation
Expand file tree
/
Copy pathCommandPaletteAddProject.tsx
More file actions
172 lines (165 loc) · 6 KB
/
Copy pathCommandPaletteAddProject.tsx
File metadata and controls
172 lines (165 loc) · 6 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import {
type AddProjectRemoteSource,
type NewProjectPublishTarget,
} from "@t3tools/client-runtime/operations/projects";
import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients";
import type { SourceControlProviderKind } from "@t3tools/contracts";
import { FolderGit2Icon, LinkIcon } from "lucide-react";
import { type ReactNode, useState } from "react";
import { sourceControlIcon } from "~/sourceControlPresentation";
import { ITEM_ICON_CLASS } from "./CommandPalette.logic";
import { CommandPaletteTitleIcon } from "./CommandPaletteResults";
import { Button } from "./ui/button";
import { Kbd, KbdGroup } from "./ui/kbd";
import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
export function remoteProjectSourceIcon(
source: AddProjectRemoteSource,
className: string,
): ReactNode {
if (source === "url") return <LinkIcon className={className} />;
const Icon = sourceControlIcon(sourceControlClients.get(source));
return <Icon className={className} />;
}
export function sourceControlHostIcon(
definition: NewProjectPublishTarget["definition"],
className: string,
): ReactNode {
const Icon = sourceControlIcon(definition);
return <Icon className={className} />;
}
/** The input's trailing submit button for the add-project, clone, and new-project flows. */
export function CommandPaletteSubmitButton(props: {
readonly id: string;
readonly label: ReactNode;
readonly shortcutLabel: string;
readonly ariaLabel: string;
readonly tooltip: ReactNode;
readonly disabled: boolean;
readonly onSubmit: () => void;
}) {
return (
<Tooltip>
<TooltipTrigger
render={
<Button
id={props.id}
variant="outline"
size="xs"
className="absolute inset-e-2.5 top-1/2 -translate-y-1/2"
aria-label={props.ariaLabel}
disabled={props.disabled}
onMouseDown={(event) => {
event.preventDefault();
}}
onClick={props.onSubmit}
/>
}
>
<span>{props.label}</span>
<KbdGroup className="pointer-events-none -me-0.5">
<Kbd>{props.shortcutLabel}</Kbd>
</KbdGroup>
</TooltipTrigger>
<TooltipPopup side="top">{props.tooltip}</TooltipPopup>
</Tooltip>
);
}
/**
* Picks which host a new project publishes to. The row it sits in toggles on click, so the
* picker keeps its clicks to itself. The row also cancels pointerdown to keep focus in the
* input, which drops the mousedown Select opens on, so the picker opens on click instead.
*/
export function NewProjectPublishHostPicker(props: {
readonly disabled: boolean;
readonly targets: ReadonlyArray<NewProjectPublishTarget>;
readonly value: SourceControlProviderKind;
readonly onValueChange: (kind: SourceControlProviderKind) => void;
}) {
const [open, setOpen] = useState(false);
return (
<span
className="flex"
onClick={(event) => {
event.stopPropagation();
// React bubbles the popup's clicks here through its portal; only the
// trigger opens the picker.
if (!event.currentTarget.contains(event.target as Node)) return;
if (!props.disabled) setOpen(true);
}}
>
<Select
disabled={props.disabled}
open={open}
onOpenChange={setOpen}
value={props.value}
onValueChange={(kind) => {
if (kind !== null) props.onValueChange(kind);
}}
>
<SelectTrigger size="xs" variant="ghost" aria-label="Host for the new repository">
<SelectValue>
{(kind: SourceControlProviderKind) => sourceControlClients.get(kind).label}
</SelectValue>
</SelectTrigger>
<SelectPopup align="end" alignItemWithTrigger={false}>
{props.targets.map((target) => (
<SelectItem key={target.definition.kind} hideIndicator value={target.definition.kind}>
<span className="inline-flex items-center gap-1.5">
{sourceControlHostIcon(target.definition, "size-3.5")}
{target.definition.label}
</span>
</SelectItem>
))}
</SelectPopup>
</Select>
</span>
);
}
export function NewProjectSummary(props: {
readonly name: string;
readonly pathPreview: string;
readonly projectsRoot: string | null;
readonly environmentLabel: string | null;
}) {
return (
<div className="p-2 pb-0">
<div className="flex min-h-8 items-center gap-2 rounded-sm px-2 py-1.5">
<CommandPaletteTitleIcon>
<FolderGit2Icon className={ITEM_ICON_CLASS} />
</CommandPaletteTitleIcon>
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-foreground text-sm">
{props.name.length > 0 ? props.name : "New project"}
</span>
<span className="truncate text-muted-foreground/85 text-xs">
{props.name.length > 0
? `Creates ${props.pathPreview}`
: `Goes in ${props.projectsRoot}`}
{props.environmentLabel === null ? null : ` on ${props.environmentLabel}`}
</span>
</span>
</div>
</div>
);
}
export function RemoteRepositorySummary(props: {
readonly source: AddProjectRemoteSource;
readonly title: string;
readonly description: string;
}) {
return (
<div className="shrink-0 p-2 pb-0">
<div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Repository</div>
<div className="flex min-h-8 items-center gap-2 rounded-sm px-2 py-1.5">
<CommandPaletteTitleIcon>
{remoteProjectSourceIcon(props.source, ITEM_ICON_CLASS)}
</CommandPaletteTitleIcon>
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-foreground text-sm">{props.title}</span>
<span className="truncate text-muted-foreground/85 text-xs">{props.description}</span>
</span>
</div>
</div>
);
}