|
| 1 | +import { useExtensionState } from "@/context/ExtensionStateContext" |
| 2 | +import styled from "styled-components" |
| 3 | +import { cn } from "@src/lib/utils" |
| 4 | +import { type ExtensionState } from "@roo/ExtensionMessage" |
| 5 | +import { StandardTooltip } from "@src/components/ui" |
| 6 | +import { useTranslation } from "react-i18next" |
| 7 | +import { vscode } from "@/utils/vscode" |
| 8 | +import { type ZgsmCodeMode } from "@roo/modes" |
| 9 | + |
| 10 | +const mapDisplayToOriginal = (displayMode: "vibe" | "plan" | "spec"): string => { |
| 11 | + if (displayMode === "vibe") return "vibe" |
| 12 | + if (displayMode === "plan") return "plan" |
| 13 | + if (displayMode === "spec") return "strict" |
| 14 | + return displayMode |
| 15 | +} |
| 16 | + |
| 17 | +const mapModeToDisplay = (mode: ExtensionState["zgsmCodeMode"]): "vibe" | "plan" | "spec" => { |
| 18 | + if (mode === "vibe") return "vibe" |
| 19 | + if (mode === "plan") return "plan" |
| 20 | + if (mode === "strict") return "spec" |
| 21 | + return mode as "vibe" | "plan" | "spec" |
| 22 | +} |
| 23 | + |
| 24 | +const SwitchContainer = styled.div<{ disabled: boolean }>` |
| 25 | + display: flex; |
| 26 | + align-items: center; |
| 27 | + background-color: transparent; |
| 28 | + border: 1px solid var(--vscode-input-border); |
| 29 | + border-radius: 12px; |
| 30 | + overflow: hidden; |
| 31 | + cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; |
| 32 | + opacity: ${(props) => (props.disabled ? 0.5 : 1)}; |
| 33 | + transform: scale(1); |
| 34 | + transform-origin: right center; |
| 35 | + margin-left: 0; |
| 36 | + user-select: none; |
| 37 | +` |
| 38 | + |
| 39 | +const Slider = styled.div.withConfig({ |
| 40 | + shouldForwardProp: (prop) => !["isVibe", "isPlan", "isSpec"].includes(prop), |
| 41 | +})<{ isVibe: boolean; isPlan?: boolean; isSpec?: boolean }>` |
| 42 | + position: absolute; |
| 43 | + height: 100%; |
| 44 | + width: 33.33%; |
| 45 | + background-color: var(--vscode-focusBorder); |
| 46 | + transition: transform 0.2s ease; |
| 47 | + transform: translateX(${(props) => (props.isVibe ? "0%" : props.isSpec ? "200%" : "100%")}); |
| 48 | +` |
| 49 | + |
| 50 | +export const ModeSwitch = () => { |
| 51 | + const { zgsmCodeMode, setZgsmCodeMode } = useExtensionState() |
| 52 | + const displayMode = mapModeToDisplay(zgsmCodeMode) |
| 53 | + const { t } = useTranslation("welcome") |
| 54 | + |
| 55 | + const handleModeClick = (selectedMode: "vibe" | "plan" | "spec", forceMode?: string) => { |
| 56 | + const originalMode = mapDisplayToOriginal(selectedMode) |
| 57 | + setZgsmCodeMode(originalMode as ZgsmCodeMode) |
| 58 | + |
| 59 | + vscode.postMessage({ |
| 60 | + type: "zgsmCodeMode", |
| 61 | + text: originalMode, |
| 62 | + }) |
| 63 | + |
| 64 | + vscode.postMessage({ |
| 65 | + type: "mode", |
| 66 | + text: forceMode || (originalMode === "vibe" ? "code" : "strict"), |
| 67 | + }) |
| 68 | + } |
| 69 | + |
| 70 | + const getModeTip = (mode: string) => { |
| 71 | + if (mode === "vibe") { |
| 72 | + return t("vibe.description") |
| 73 | + } else if (mode === "spec") { |
| 74 | + return t("strict.description") |
| 75 | + } else if (mode === "plan") { |
| 76 | + return t("plan.description") |
| 77 | + } |
| 78 | + return "" |
| 79 | + } |
| 80 | + |
| 81 | + return ( |
| 82 | + <SwitchContainer data-testid="mode-switch" disabled={false}> |
| 83 | + <Slider isVibe={displayMode === "vibe"} isPlan={displayMode === "plan"} isSpec={displayMode === "spec"} /> |
| 84 | + {["Vibe", "Plan", "Spec"].map((m) => ( |
| 85 | + <StandardTooltip content={getModeTip(m.toLowerCase())} key={m}> |
| 86 | + <div |
| 87 | + aria-checked={displayMode === m.toLowerCase()} |
| 88 | + className={cn( |
| 89 | + "pt-0.5 pb-px px-2 z-10 text-xs w-1/3 text-center bg-transparent cursor-pointer", |
| 90 | + displayMode === m.toLowerCase() ? "text-white" : "text-input-foreground", |
| 91 | + )} |
| 92 | + onClick={() => |
| 93 | + handleModeClick( |
| 94 | + m.toLowerCase() as "vibe" | "plan" | "spec", |
| 95 | + m === "Plan" ? "plan" : undefined, |
| 96 | + ) |
| 97 | + } |
| 98 | + role="switch"> |
| 99 | + {m} |
| 100 | + </div> |
| 101 | + </StandardTooltip> |
| 102 | + ))} |
| 103 | + </SwitchContainer> |
| 104 | + ) |
| 105 | +} |
0 commit comments