| ... | ... | @@ -8,6 +8,7 @@ import type { |
| 8 | 8 | import { getTerminalLabel } from "@t3tools/shared/terminalLabels"; |
| 9 | 9 | import { |
| 10 | 10 | Bot, |
| 11 | ChevronDown, |
| 11 | 12 | FileDiff, |
| 12 | 13 | Files, |
| 13 | 14 | GitPullRequest, |
| ... | ... | @@ -283,6 +284,8 @@ function SurfaceMenuItem(props: { |
| 283 | 284 | */ |
| 284 | 285 | function RightPanelEmptyState(props: { |
| 285 | 286 | onAddBrowser: () => void; |
| 287 | onAddBrowserInProfile: (profileId: string) => void; |
| 288 | browserProfiles: ReadonlyArray<{ readonly id: string; readonly name: string }>; |
| 286 | 289 | onAddTerminal: () => void; |
| 287 | 290 | onAddDiff: () => void; |
| 288 | 291 | onAddFiles: () => void; |
| ... | ... | @@ -473,31 +476,78 @@ function RightPanelEmptyState(props: { |
| 473 | 476 | <div className="grid grid-cols-2 gap-2"> |
| 474 | 477 | {actions.map((action) => |
| 475 | 478 | action.available ? ( |
| 476 | | <button |
| 479 | // The card is itself a button, so the profile chooser sits beside |
| 480 | // it in a wrapper rather than inside it. Hover lives on the |
| 481 | // wrapper: the chooser overlays the card, and a pointer moving |
| 482 | // onto it must not read as leaving the card. |
| 483 | <div |
| 477 | 484 | key={action.label} |
| 478 | | type="button" |
| 479 | | onClick={action.onClick} |
| 485 | className="group relative" |
| 480 | 486 | onMouseEnter={() => setHighlight(availableActions.indexOf(action))} |
| 481 | 487 | onMouseLeave={() => |
| 482 | 488 | setHighlight((current) => |
| 483 | 489 | current === availableActions.indexOf(action) ? -1 : current, |
| 484 | 490 | ) |
| 485 | 491 | } |
| 486 | | className={cn( |
| 487 | | "relative flex w-full cursor-pointer flex-col items-start p-4 text-left transition hover:border-border hover:bg-accent/60", |
| 488 | | cardShellClass, |
| 489 | | isHighlighted(action) && highlightedCardClass, |
| 490 | | )} |
| 491 | 492 | > |
| 492 | | <Kbd className="absolute top-3 right-3">{action.shortcut}</Kbd> |
| 493 | | <span className="flex items-center gap-2 pe-8"> |
| 494 | | {actionIcon(action)} |
| 495 | | <span className="font-medium text-sm">{action.label}</span> |
| 496 | | </span> |
| 497 | | <span className="mt-1.5 text-muted-foreground text-xs leading-relaxed"> |
| 498 | | {action.description} |
| 499 | | </span> |
| 500 | | </button> |
| 493 | <button |
| 494 | type="button" |
| 495 | onClick={action.onClick} |
| 496 | className={cn( |
| 497 | // Full height: the wrapper is the grid item that stretches |
| 498 | // to the row, so the button must fill it to stay level with |
| 499 | // its neighbour and keep the chooser anchored inside. |
| 500 | "relative flex h-full w-full cursor-pointer flex-col items-start p-4 text-left transition group-hover:border-border group-hover:bg-accent/60", |
| 501 | cardShellClass, |
| 502 | isHighlighted(action) && highlightedCardClass, |
| 503 | )} |
| 504 | > |
| 505 | <Kbd className="absolute top-3 right-3">{action.shortcut}</Kbd> |
| 506 | <span className="flex items-center gap-2 pe-8"> |
| 507 | {actionIcon(action)} |
| 508 | <span className="font-medium text-sm">{action.label}</span> |
| 509 | </span> |
| 510 | <span className="mt-1.5 text-muted-foreground text-xs leading-relaxed"> |
| 511 | {action.description} |
| 512 | </span> |
| 513 | </button> |
| 514 | {/* |
| 515 | Same choice the tab bar's "+" menu offers: the card opens the |
| 516 | default profile, the chevron picks another. Only worth showing |
| 517 | once there is something to choose between. |
| 518 | */} |
| 519 | {action.label === "Browser" && props.browserProfiles.length > 1 ? ( |
| 520 | <Menu> |
| 521 | <MenuTrigger |
| 522 | render={ |
| 523 | <Button |
| 524 | aria-label="Open browser in a profile" |
| 525 | className="absolute right-3 bottom-3 [--control-icon-color:currentColor]" |
| 526 | size="icon-xs" |
| 527 | variant="ghost-muted" |
| 528 | /> |
| 529 | } |
| 530 | > |
| 531 | <ChevronDown className="size-3.5" /> |
| 532 | </MenuTrigger> |
| 533 | <MenuPopup |
| 534 | align="end" |
| 535 | side="bottom" |
| 536 | sideOffset={6} |
| 537 | className="min-w-40 max-w-56" |
| 538 | > |
| 539 | {props.browserProfiles.map((profile) => ( |
| 540 | <MenuItem |
| 541 | key={profile.id} |
| 542 | onClick={() => props.onAddBrowserInProfile(profile.id)} |
| 543 | > |
| 544 | <span className="min-w-0 truncate">{profile.name}</span> |
| 545 | </MenuItem> |
| 546 | ))} |
| 547 | </MenuPopup> |
| 548 | </Menu> |
| 549 | ) : null} |
| 550 | </div> |
| 501 | 551 | ) : ( |
| 502 | 552 | <div |
| 503 | 553 | key={action.label} |
| ... | ... | @@ -1053,6 +1103,8 @@ export function RightPanelTabs(props: RightPanelTabsProps) { |
| 1053 | 1103 | {props.activeSurfaceId === null ? ( |
| 1054 | 1104 | <RightPanelEmptyState |
| 1055 | 1105 | onAddBrowser={props.onAddBrowser} |
| 1106 | onAddBrowserInProfile={props.onAddBrowserInProfile} |
| 1107 | browserProfiles={browserProfiles} |
| 1056 | 1108 | onAddTerminal={props.onAddTerminal} |
| 1057 | 1109 | onAddDiff={props.onAddDiff} |
| 1058 | 1110 | onAddFiles={props.onAddFiles} |