| ... | @@ -8,6 +8,7 @@ import type { | ... | @@ -8,6 +8,7 @@ import type { |
| 8 | import { getTerminalLabel } from "@t3tools/shared/terminalLabels"; | 8 | import { getTerminalLabel } from "@t3tools/shared/terminalLabels"; |
| 9 | import { | 9 | import { |
| 10 | Bot, | 10 | Bot, |
| | 11 | ChevronDown, |
| 11 | FileDiff, | 12 | FileDiff, |
| 12 | Files, | 13 | Files, |
| 13 | GitPullRequest, | 14 | GitPullRequest, |
| ... | @@ -283,6 +284,8 @@ function SurfaceMenuItem(props: { | ... | @@ -283,6 +284,8 @@ function SurfaceMenuItem(props: { |
| 283 | */ | 284 | */ |
| 284 | function RightPanelEmptyState(props: { | 285 | function RightPanelEmptyState(props: { |
| 285 | onAddBrowser: () => void; | 286 | onAddBrowser: () => void; |
| | 287 | onAddBrowserInProfile: (profileId: string) => void; |
| | 288 | browserProfiles: ReadonlyArray<{ readonly id: string; readonly name: string }>; |
| 286 | onAddTerminal: () => void; | 289 | onAddTerminal: () => void; |
| 287 | onAddDiff: () => void; | 290 | onAddDiff: () => void; |
| 288 | onAddFiles: () => void; | 291 | onAddFiles: () => void; |
| ... | @@ -473,31 +476,78 @@ function RightPanelEmptyState(props: { | ... | @@ -473,31 +476,78 @@ function RightPanelEmptyState(props: { |
| 473 | <div className="grid grid-cols-2 gap-2"> | 476 | <div className="grid grid-cols-2 gap-2"> |
| 474 | {actions.map((action) => | 477 | {actions.map((action) => |
| 475 | action.available ? ( | 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 | key={action.label} | 484 | key={action.label} |
| 478 | type="button" | 485 | className="group relative" |
| 479 | onClick={action.onClick} | | |
| 480 | onMouseEnter={() => setHighlight(availableActions.indexOf(action))} | 486 | onMouseEnter={() => setHighlight(availableActions.indexOf(action))} |
| 481 | onMouseLeave={() => | 487 | onMouseLeave={() => |
| 482 | setHighlight((current) => | 488 | setHighlight((current) => |
| 483 | current === availableActions.indexOf(action) ? -1 : current, | 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 | <button |
| 493 | <span className="flex items-center gap-2 pe-8"> | 494 | type="button" |
| 494 | {actionIcon(action)} | 495 | onClick={action.onClick} |
| 495 | <span className="font-medium text-sm">{action.label}</span> | 496 | className={cn( |
| 496 | </span> | 497 | // Full height: the wrapper is the grid item that stretches |
| 497 | <span className="mt-1.5 text-muted-foreground text-xs leading-relaxed"> | 498 | // to the row, so the button must fill it to stay level with |
| 498 | {action.description} | 499 | // its neighbour and keep the chooser anchored inside. |
| 499 | </span> | 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", |
| 500 | </button> | 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 | <div | 552 | <div |
| 503 | key={action.label} | 553 | key={action.label} |
| ... | @@ -1053,6 +1103,8 @@ export function RightPanelTabs(props: RightPanelTabsProps) { | ... | @@ -1053,6 +1103,8 @@ export function RightPanelTabs(props: RightPanelTabsProps) { |
| 1053 | {props.activeSurfaceId === null ? ( | 1103 | {props.activeSurfaceId === null ? ( |
| 1054 | <RightPanelEmptyState | 1104 | <RightPanelEmptyState |
| 1055 | onAddBrowser={props.onAddBrowser} | 1105 | onAddBrowser={props.onAddBrowser} |
| | 1106 | onAddBrowserInProfile={props.onAddBrowserInProfile} |
| | 1107 | browserProfiles={browserProfiles} |
| 1056 | onAddTerminal={props.onAddTerminal} | 1108 | onAddTerminal={props.onAddTerminal} |
| 1057 | onAddDiff={props.onAddDiff} | 1109 | onAddDiff={props.onAddDiff} |
| 1058 | onAddFiles={props.onAddFiles} | 1110 | onAddFiles={props.onAddFiles} |