# Files changed · PR \#22707 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/22707/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[refactor\(web\): the command palette's add-project views are components](https://git.cafe/pingdotgg/t3code/pull/22707.md)

Version: 6

Head commit: 6e79cfb6e523741013cfb58b0fcf457927146306

Base commit: 7e7936fe093e918a198bcd32dbe84a3fd45d118b

Comparison base: 7e7936fe093e918a198bcd32dbe84a3fd45d118b

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/src/components/CommandPalette.tsx

Status: modified; +73; −207

```diff
@@ -12,7 +12,6 @@
   getNewProjectPublishTarget,
   getNewProjectPublishTargets,
   getNewProjectRepository,
-  type NewProjectPublishTarget,
   normalizePastedCloneUrl,
   addProjectRemoteSourceLabel,
   addProjectRemoteSourcePathHint,
@@ -196,6 +195,14 @@
   buildNewThreadInProjectItems,
   buildProjectSearchItems,
 } from "./CommandPaletteProjectItems";
+import {
+  CommandPaletteSubmitButton,
+  NewProjectPublishHostPicker,
+  NewProjectSummary,
+  RemoteRepositorySummary,
+  remoteProjectSourceIcon,
+  sourceControlHostIcon,
+} from "./CommandPaletteAddProject";
 import { useCommandPaletteThreadItems } from "./CommandPaletteThreadItems";
 import {
   APPEARANCE_OPTIONS,
@@ -203,11 +210,9 @@
   useCommandPaletteThemeItems,
 } from "./CommandPaletteThemeItems";
 import {
-  CommandPaletteTitleIcon,
   CommandPaletteVirtualizedResults,
   scrollCommandPaletteRowIntoView,
 } from "./CommandPaletteResults";
-import { sourceControlIcon } from "~/sourceControlPresentation";
 import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";
 import { Checkbox } from "./ui/checkbox";
 import { ProjectFilePicker } from "./files/ProjectFilePicker";
@@ -219,8 +224,6 @@
 import { resolveShortcutCommand, threadJumpIndexFromCommand } from "../keybindings";
 import { CommandDialog, CommandDialogPopup, CommandFooterAction } from "./ui/command";
 import { Button } from "./ui/button";
-import { Kbd, KbdGroup } from "./ui/kbd";
-import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select";
 import { stackedThreadToast, toastManager } from "./ui/toast";
 import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
 import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext";
@@ -232,20 +235,6 @@
 
 const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = [];
 
-function remoteProjectSourceIcon(source: AddProjectRemoteSource, className: string): ReactNode {
-  if (source === "url") return <LinkIcon className={className} />;
-  const Icon = sourceControlIcon(sourceControlClients.get(source));
-  return <Icon className={className} />;
-}
-
-function sourceControlHostIcon(
-  definition: NewProjectPublishTarget["definition"],
-  className: string,
-): ReactNode {
-  const Icon = sourceControlIcon(definition);
-  return <Icon className={className} />;
-}
-
 export function CommandPalette({ children }: { children: ReactNode }) {
   const navigate = useNavigate();
   const [state, dispatch] = useReducer(reduceCommandPaletteUiState, {
@@ -599,7 +588,6 @@
   const [newProjectPublishes, setNewProjectPublishes] = useState(false);
   const [newProjectPublishKind, setNewProjectPublishKind] =
     useState<SourceControlProviderKind | null>(null);
-  const [newProjectHostPickerOpen, setNewProjectHostPickerOpen] = useState(false);
   const [isCreatingNewProject, setIsCreatingNewProject] = useState(false);
   // State lags a render behind, so a repeated Enter could start a second create.
   const newProjectSubmittingRef = useRef(false);
@@ -2091,18 +2079,6 @@
     group.value === "directories" ? { ...group, label: "Select where to clone" } : group,
   );
 
-  const remoteProjectContext = (() => {
-    if (addProjectCloneFlow?.step !== "confirm") {
-      return null;
-    }
-
-    return {
-      title: addProjectCloneFlow.repository?.nameWithOwner ?? addProjectCloneFlow.repositoryInput,
-      description: addProjectCloneFlow.repository?.url ?? addProjectCloneFlow.remoteUrl,
-      icon: remoteProjectSourceIcon(addProjectCloneFlow.source, ITEM_ICON_CLASS),
-    };
-  })();
-
   const newProjectsRoot = newProjectFlow ? newProjectsRootFor(newProjectFlow.environmentId) : null;
   const newProjectPathPreview =
     newProjectsRoot === null ? null : getNewProjectPathPreview(newProjectsRoot, newProjectName);
@@ -2209,55 +2185,12 @@
                 titleTrailingContent: (
                   <span className="ms-auto flex items-center gap-2">
                     {newProjectPublishTargets.length > 1 ? (
-                      // The row 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.
-                      <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 (!isCreatingNewProject) setNewProjectHostPickerOpen(true);
-                        }}
-                      >
-                        <Select
-                          disabled={isCreatingNewProject}
-                          open={newProjectHostPickerOpen}
-                          onOpenChange={setNewProjectHostPickerOpen}
-                          value={newProjectPublishTarget.definition.kind}
-                          onValueChange={(kind) => {
-                            if (kind !== null) setNewProjectPublishKind(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}>
-                            {newProjectPublishTargets.map((target) => (
-                              <SelectItem
-                                key={target.definition.kind}
-                                hideIndicator
-                                value={target.definition.kind}
+                      <NewProjectPublishHostPicker
+                        disabled={isCreatingNewProject}
+                        targets={newProjectPublishTargets}
+                        value={newProjectPublishTarget.definition.kind}
+                        onValueChange={setNewProjectPublishKind}
-                              >
-                                <span className="inline-flex items-center gap-1.5">
-                                  {sourceControlHostIcon(target.definition, "size-3.5")}
+                      />
-                                  {target.definition.label}
-                                </span>
-                              </SelectItem>
-                            ))}
-                          </SelectPopup>
-                        </Select>
-                      </span>
                     ) : null}
                     <span className="pointer-events-none flex">
                       <Checkbox checked={newProjectPublishes} tabIndex={-1} aria-hidden />
@@ -2622,106 +2555,56 @@
 
   const inputAccessory =
     newProjectFlow !== null ? (
-      <Tooltip>
-        <TooltipTrigger
-          render={
-            <Button
-              variant="outline"
-              size="xs"
-              tabIndex={-1}
-              className="absolute inset-e-2.5 top-1/2 -translate-y-1/2"
-              aria-label={`Create (${createShortcutLabel})`}
-              disabled={!canSubmitNewProject}
-              onMouseDown={(event) => {
+      <CommandPaletteSubmitButton
-                event.preventDefault();
-              }}
-              onClick={() => {
-                void submitNewProject();
-              }}
-            />
-          }
+        label={isCreatingNewProject ? "Creating" : "Create"}
+        shortcutLabel={createShortcutLabel}
+        ariaLabel={`Create (${createShortcutLabel})`}
+        tooltip={`Create (${createShortcutLabel})`}
+        disabled={!canSubmitNewProject}
+        onSubmit={() => {
+          void submitNewProject();
+        }}
+      />
-        >
-          <span>{isCreatingNewProject ? "Creating" : "Create"}</span>
-          <KbdGroup className="pointer-events-none -me-0.5">
-            <Kbd>{createShortcutLabel}</Kbd>
-          </KbdGroup>
-        </TooltipTrigger>
-        <TooltipPopup side="top">{`Create (${createShortcutLabel})`}</TooltipPopup>
-      </Tooltip>
     ) : addProjectCloneFlow?.step === "repository" ? (
-      <Tooltip>
-        <TooltipTrigger
-          render={
-            <Button
-              variant="outline"
-              size="xs"
-              tabIndex={-1}
-              className="absolute inset-e-2.5 top-1/2 -translate-y-1/2"
-              aria-label={`${remoteProjectButtonLabel ?? "Continue"} (Enter)`}
-              disabled={!canSubmitRemoteProjectFlow}
+      <CommandPaletteSubmitButton
-              onMouseDown={(event) => {
-                event.preventDefault();
-              }}
-              onClick={() => {
-                void submitAddProjectCloneFlow();
-              }}
-            />
+        label={isRemoteProjectPending ? "Working" : remoteProjectButtonLabel}
+        shortcutLabel="Enter"
+        ariaLabel={`${remoteProjectButtonLabel ?? "Continue"} (Enter)`}
+        tooltip={`${remoteProjectButtonLabel ?? "Continue"} (Enter)`}
+        disabled={!canSubmitRemoteProjectFlow}
+        onSubmit={() => {
+          void submitAddProjectCloneFlow();
+        }}
+      />
-          }
-        >
-          <span>{isRemoteProjectPending ? "Working" : remoteProjectButtonLabel}</span>
-          <KbdGroup className="pointer-events-none -me-0.5">
-            <Kbd>Enter</Kbd>
-          </KbdGroup>
-        </TooltipTrigger>
-        <TooltipPopup side="top">{remoteProjectButtonLabel ?? "Continue"} (Enter)</TooltipPopup>
-      </Tooltip>
     ) : isBrowsing ? (
-      <Tooltip>
-        <TooltipTrigger
-          render={
-            <Button
-              variant="outline"
-              size="xs"
-              tabIndex={-1}
-              className="absolute inset-e-2.5 top-1/2 -translate-y-1/2"
-              aria-label={`${submitActionLabel} (${addShortcutLabel})`}
-              disabled={
-                !canCreateProject ||
-                !canCreateProjectInEnvironment(browseEnvironment?.connection.phase) ||
-                relativePathNeedsActiveProject ||
-                (isCloneDestinationStep && (!canCloneProject || isRemoteProjectPending))
-              }
-              onMouseDown={(event) => {
-                event.preventDefault();
+      <CommandPaletteSubmitButton
+        label={isCloneDestinationStep && isRemoteProjectPending ? "Cloning" : submitActionLabel}
+        shortcutLabel={addShortcutLabel}
-              }}
-              onClick={() => {
-                if (relativePathNeedsActiveProject) {
-                  return;
-                }
-                if (isCloneDestinationStep) {
-                  void submitAddProjectCloneFlow(resolvedAddProjectPath);
-                } else {
-                  void handleAddProject(resolvedAddProjectPath);
-                }
-              }}
-            />
-          }
-        >
-          <span>
-            {isCloneDestinationStep && isRemoteProjectPending ? "Cloning" : submitActionLabel}
-          </span>
-          <KbdGroup className="pointer-events-none -me-0.5">
-            <Kbd>{hasHighlightedBrowseItem ? `${submitModifierLabel} Enter` : "Enter"}</Kbd>
-          </KbdGroup>
-        </TooltipTrigger>
-        <TooltipPopup side="top">
-          {isCloneDestinationStep && !canCloneProject
+        ariaLabel={`${submitActionLabel} (${addShortcutLabel})`}
+        tooltip={
+          isCloneDestinationStep && !canCloneProject
             ? "This connection needs permission to write source control and add projects."
             : canCreateProject
               ? `${submitActionLabel} (${addShortcutLabel})`
+              : "This connection cannot add projects."
+        }
+        disabled={
+          !canCreateProject ||
+          !canCreateProjectInEnvironment(browseEnvironment?.connection.phase) ||
+          relativePathNeedsActiveProject ||
+          (isCloneDestinationStep && (!canCloneProject || isRemoteProjectPending))
+        }
+        onSubmit={() => {
+          if (relativePathNeedsActiveProject) {
+            return;
+          }
+          if (isCloneDestinationStep) {
+            void submitAddProjectCloneFlow(resolvedAddProjectPath);
+          } else {
+            void handleAddProject(resolvedAddProjectPath);
+          }
+        }}
-              : "This connection cannot add projects."}
+      />
-        </TooltipPopup>
-      </Tooltip>
     ) : null;
 
   const footerActionLabel =
@@ -2803,38 +2686,21 @@
       value={query}
     >
       {newProjectPathPreview !== null ? (
-        <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">
-                {newProjectName.length > 0 ? newProjectName : "New project"}
-              </span>
-              <span className="truncate text-muted-foreground/85 text-xs">
-                {newProjectName.length > 0
+        <NewProjectSummary
-                  ? `Creates ${newProjectPathPreview}`
-                  : `Goes in ${newProjectsRoot}`}
-                {newProjectEnvironmentLabel === null ? null : ` on ${newProjectEnvironmentLabel}`}
-              </span>
-            </span>
-          </div>
-        </div>
+          name={newProjectName}
+          pathPreview={newProjectPathPreview}
+          projectsRoot={newProjectsRoot}
+          environmentLabel={newProjectEnvironmentLabel}
+        />
       ) : null}
+      {addProjectCloneFlow?.step === "confirm" ? (
+        <RemoteRepositorySummary
+          source={addProjectCloneFlow.source}
+          title={
+            addProjectCloneFlow.repository?.nameWithOwner ?? addProjectCloneFlow.repositoryInput
+          }
+          description={addProjectCloneFlow.repository?.url ?? addProjectCloneFlow.remoteUrl}
-      {remoteProjectContext ? (
+        />
-        <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>{remoteProjectContext.icon}</CommandPaletteTitleIcon>
-            <span className="flex min-w-0 flex-1 flex-col">
-              <span className="truncate text-foreground text-sm">{remoteProjectContext.title}</span>
-              <span className="truncate text-muted-foreground/85 text-xs">
-                {remoteProjectContext.description}
-              </span>
-            </span>
-          </div>
-        </div>
       ) : null}
       {browseAccessError ? (
         <div role="alert" className="px-4 py-3 text-sm text-muted-foreground">
```

## apps/web/src/components/CommandPaletteAddProject.tsx

Status: added; +171; −0

```diff
@@ -1,0 +1,171 @@
+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 label: ReactNode;
+  readonly shortcutLabel: string;
+  readonly ariaLabel: string;
+  readonly tooltip: ReactNode;
+  readonly disabled: boolean;
+  readonly onSubmit: () => void;
+}) {
+  return (
+    <Tooltip>
+      <TooltipTrigger
+        render={
+          <Button
+            variant="outline"
+            size="xs"
+            tabIndex={-1}
+            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>
+  );
+}
```
