Browse Source

Add custom Button component with variant system (#9150)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
Co-authored-by: Roo Code <[email protected]>
Bruno Bergher 1 month ago
parent
commit
dc37f6ae76
24 changed files with 123 additions and 128 deletions
  1. 16 31
      webview-ui/src/components/chat/AutoApproveDropdown.tsx
  2. 3 3
      webview-ui/src/components/chat/AutoApprovedRequestLimitWarning.tsx
  3. 5 5
      webview-ui/src/components/chat/BrowserSessionRow.tsx
  4. 11 11
      webview-ui/src/components/chat/ChatView.tsx
  5. 7 6
      webview-ui/src/components/chat/CodeIndexPopover.tsx
  6. 2 4
      webview-ui/src/components/chat/FollowUpSuggest.tsx
  7. 1 1
      webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
  8. 7 13
      webview-ui/src/components/cloud/CloudView.tsx
  9. 19 11
      webview-ui/src/components/common/VSCodeButtonLink.tsx
  10. 1 1
      webview-ui/src/components/common/VersionIndicator.tsx
  11. 2 2
      webview-ui/src/components/history/HistoryView.tsx
  12. 1 1
      webview-ui/src/components/marketplace/MarketplaceView.tsx
  13. 1 1
      webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx
  14. 4 5
      webview-ui/src/components/mcp/McpView.tsx
  15. 5 5
      webview-ui/src/components/modes/ModesView.tsx
  16. 1 1
      webview-ui/src/components/settings/ApiConfigManager.tsx
  17. 2 5
      webview-ui/src/components/settings/AutoApproveToggle.tsx
  18. 13 4
      webview-ui/src/components/settings/BrowserSettings.tsx
  19. 1 1
      webview-ui/src/components/settings/PromptsSettings.tsx
  20. 1 1
      webview-ui/src/components/settings/SettingsView.tsx
  21. 4 5
      webview-ui/src/components/settings/providers/Roo.tsx
  22. 6 1
      webview-ui/src/components/settings/providers/__tests__/HuggingFace.spec.tsx
  23. 6 7
      webview-ui/src/components/ui/button.tsx
  24. 4 3
      webview-ui/src/components/welcome/WelcomeView.tsx

+ 16 - 31
webview-ui/src/components/chat/AutoApproveDropdown.tsx

@@ -6,7 +6,7 @@ import { cn } from "@/lib/utils"
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { useAppTranslation } from "@/i18n/TranslationContext"
 import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
-import { Popover, PopoverContent, PopoverTrigger, StandardTooltip, ToggleSwitch } from "@/components/ui"
+import { Popover, PopoverContent, PopoverTrigger, StandardTooltip, ToggleSwitch, Button } from "@/components/ui"
 import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
 import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles"
 import { useAutoApprovalState } from "@/hooks/useAutoApprovalState"
@@ -228,24 +228,21 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
 							const isEnabled = toggles[key]
 							return (
 								<StandardTooltip key={key} content={t(descriptionKey)}>
-									<button
+									<Button
+										variant={isEnabled ? "primary" : "secondary"}
 										onClick={() => onAutoApproveToggle(key, !isEnabled)}
 										className={cn(
-											"flex items-center gap-2 px-2 py-2 rounded text-sm text-left",
+											"flex items-center gap-2 px-2 py-2 text-sm text-left justify-start h-auto",
 											"transition-all duration-150",
-											"opacity-100 hover:opacity-70",
-											"cursor-pointer",
 											!effectiveAutoApprovalEnabled &&
 												"opacity-50 cursor-not-allowed hover:opacity-50",
-											isEnabled
-												? "bg-vscode-button-background text-vscode-button-foreground"
-												: "bg-vscode-button-background/15 text-vscode-foreground hover:bg-vscode-list-hoverBackground",
+											!isEnabled && "bg-vscode-button-background/15",
 										)}
 										disabled={!effectiveAutoApprovalEnabled}
 										data-testid={`auto-approve-${key}`}>
 										<span className={`codicon codicon-${icon} text-sm flex-shrink-0`} />
 										<span className="flex-1 truncate">{t(labelKey)}</span>
-									</button>
+									</Button>
 								</StandardTooltip>
 							)
 						})}
@@ -254,44 +251,32 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
 					{/* Bottom bar with Select All/None buttons */}
 					<div className="flex flex-row items-center justify-between px-2 py-2 border-t border-vscode-dropdown-border">
 						<div className="flex flex-row gap-1">
-							<button
+							<Button
+								variant="ghost"
+								size="sm"
 								aria-label={t("chat:autoApprove.selectAll")}
 								onClick={handleSelectAll}
 								disabled={!effectiveAutoApprovalEnabled}
 								className={cn(
-									"relative inline-flex items-center justify-center gap-1",
-									"bg-transparent border-none px-2 py-1",
-									"rounded-md text-base font-bold",
-									"text-vscode-foreground",
-									"transition-all duration-150",
-									"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
-									"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
-									"active:bg-[rgba(255,255,255,0.1)]",
-									"cursor-pointer",
+									"gap-1 px-2 py-1 text-base font-bold h-auto",
 									!effectiveAutoApprovalEnabled && "opacity-50 hover:opacity-50 cursor-not-allowed",
 								)}>
 								<ListChecks className="w-3.5 h-3.5" />
 								<span>{t("chat:autoApprove.all")}</span>
-							</button>
-							<button
+							</Button>
+							<Button
+								variant="ghost"
+								size="sm"
 								aria-label={t("chat:autoApprove.selectNone")}
 								onClick={handleSelectNone}
 								disabled={!effectiveAutoApprovalEnabled}
 								className={cn(
-									"relative inline-flex items-center justify-center gap-1",
-									"bg-transparent border-none px-2 py-1",
-									"rounded-md text-base font-bold",
-									"text-vscode-foreground",
-									"transition-all duration-150",
-									"hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
-									"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
-									"active:bg-[rgba(255,255,255,0.1)]",
-									"cursor-pointer",
+									"gap-1 px-2 py-1 text-base font-bold h-auto",
 									!effectiveAutoApprovalEnabled && "opacity-50 hover:opacity-50 cursor-not-allowed",
 								)}>
 								<LayoutList className="w-3.5 h-3.5" />
 								<span>{t("chat:autoApprove.none")}</span>
-							</button>
+							</Button>
 						</div>
 
 						<label

+ 3 - 3
webview-ui/src/components/chat/AutoApprovedRequestLimitWarning.tsx

@@ -1,10 +1,10 @@
 import React, { memo, useState } from "react"
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 import { Trans } from "react-i18next"
 
 import type { ClineMessage } from "@roo-code/types"
 
 import { vscode } from "@src/utils/vscode"
+import { Button } from "@src/components/ui"
 
 type AutoApprovedRequestLimitWarningProps = {
 	message: ClineMessage
@@ -50,7 +50,7 @@ export const AutoApprovedRequestLimitWarning = memo(({ message }: AutoApprovedRe
 				<div className="flex justify-between items-center">
 					<Trans i18nKey={descriptionKey} ns="chat" values={{ count }} />
 				</div>
-				<VSCodeButton
+				<Button
 					style={{ width: "100%", padding: "6px", borderRadius: "4px" }}
 					onClick={(e) => {
 						e.preventDefault()
@@ -58,7 +58,7 @@ export const AutoApprovedRequestLimitWarning = memo(({ message }: AutoApprovedRe
 						vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
 					}}>
 					<Trans i18nKey={buttonKey} ns="chat" />
-				</VSCodeButton>
+				</Button>
 			</div>
 		</>
 	)

+ 5 - 5
webview-ui/src/components/chat/BrowserSessionRow.tsx

@@ -2,7 +2,6 @@ import React, { memo, useEffect, useMemo, useRef, useState } from "react"
 import { useSize } from "react-use"
 import deepEqual from "fast-deep-equal"
 import { useTranslation } from "react-i18next"
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 
 import type { ClineMessage } from "@roo-code/types"
 
@@ -10,6 +9,7 @@ import { BrowserAction, BrowserActionResult, ClineSayBrowserAction } from "@roo/
 
 import { vscode } from "@src/utils/vscode"
 import { useExtensionState } from "@src/context/ExtensionStateContext"
+import { Button } from "@src/components/ui"
 
 import CodeBlock, { CODE_BLOCK_BG_COLOR } from "../common/CodeBlock"
 import { ChatRowContent } from "./ChatRow"
@@ -372,16 +372,16 @@ const BrowserSessionRow = memo((props: BrowserSessionRowProps) => {
 						{t("chat:browser.navigation.step", { current: currentPageIndex + 1, total: pages.length })}
 					</div>
 					<div style={{ display: "flex", gap: "4px" }}>
-						<VSCodeButton
+						<Button
 							disabled={currentPageIndex === 0 || isBrowsing}
 							onClick={() => setCurrentPageIndex((i) => i - 1)}>
 							{t("chat:browser.navigation.previous")}
-						</VSCodeButton>
-						<VSCodeButton
+						</Button>
+						<Button
 							disabled={currentPageIndex === pages.length - 1 || isBrowsing}
 							onClick={() => setCurrentPageIndex((i) => i + 1)}>
 							{t("chat:browser.navigation.next")}
-						</VSCodeButton>
+						</Button>
 					</div>
 				</div>
 			)}

+ 11 - 11
webview-ui/src/components/chat/ChatView.tsx

@@ -3,7 +3,7 @@ import { useDeepCompareEffect, useEvent, useMount } from "react-use"
 import debounce from "debounce"
 import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
 import removeMd from "remove-markdown"
-import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
 import useSound from "use-sound"
 import { LRUCache } from "lru-cache"
 import { Trans } from "react-i18next"
@@ -30,7 +30,7 @@ import { useExtensionState } from "@src/context/ExtensionStateContext"
 import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
 import RooHero from "@src/components/welcome/RooHero"
 import RooTips from "@src/components/welcome/RooTips"
-import { StandardTooltip } from "@src/components/ui"
+import { StandardTooltip, Button } from "@src/components/ui"
 import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog"
 
 import TelemetryBanner from "../common/TelemetryBanner"
@@ -1464,15 +1464,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 							}`}>
 							{showScrollToBottom ? (
 								<StandardTooltip content={t("chat:scrollToBottom")}>
-									<VSCodeButton
-										appearance="secondary"
+									<Button
+										variant="secondary"
 										className="flex-[2]"
 										onClick={() => {
 											scrollToBottomSmooth()
 											disableAutoScrollRef.current = false
 										}}>
 										<span className="codicon codicon-chevron-down"></span>
-									</VSCodeButton>
+									</Button>
 								</StandardTooltip>
 							) : (
 								<>
@@ -1499,13 +1499,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 																				? t("chat:proceedWhileRunning.tooltip")
 																				: undefined
 											}>
-											<VSCodeButton
-												appearance="primary"
+											<Button
+												variant="primary"
 												disabled={!enableButtons}
 												className={secondaryButtonText ? "flex-1 mr-[6px]" : "flex-[2] mr-0"}
 												onClick={() => handlePrimaryButtonClick(inputValue, selectedImages)}>
 												{primaryButtonText}
-											</VSCodeButton>
+											</Button>
 										</StandardTooltip>
 									)}
 									{(secondaryButtonText || isStreaming) && (
@@ -1521,13 +1521,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 																? t("chat:terminate.tooltip")
 																: undefined
 											}>
-											<VSCodeButton
-												appearance="secondary"
+											<Button
+												variant="secondary"
 												disabled={!enableButtons && !(isStreaming && !didClickCancel)}
 												className={isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]"}
 												onClick={() => handleSecondaryButtonClick(inputValue, selectedImages)}>
 												{isStreaming ? t("chat:cancel.title") : secondaryButtonText}
-											</VSCodeButton>
+											</Button>
 										</StandardTooltip>
 									)}
 								</>

+ 7 - 6
webview-ui/src/components/chat/CodeIndexPopover.tsx

@@ -41,6 +41,7 @@ import {
 	PopoverContent,
 	Slider,
 	StandardTooltip,
+	Button,
 } from "@src/components/ui"
 import { useRooPortal } from "@src/components/ui/hooks/useRooPortal"
 import { useEscapeKey } from "@src/hooks/useEscapeKey"
@@ -1387,11 +1388,11 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 								{currentSettings.codebaseIndexEnabled &&
 									(indexingStatus.systemStatus === "Error" ||
 										indexingStatus.systemStatus === "Standby") && (
-										<VSCodeButton
+										<Button
 											onClick={() => vscode.postMessage({ type: "startIndexing" })}
 											disabled={saveStatus === "saving" || hasUnsavedChanges}>
 											{t("settings:codeIndex.startIndexingButton")}
-										</VSCodeButton>
+										</Button>
 									)}
 
 								{currentSettings.codebaseIndexEnabled &&
@@ -1399,9 +1400,9 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 										indexingStatus.systemStatus === "Error") && (
 										<AlertDialog>
 											<AlertDialogTrigger asChild>
-												<VSCodeButton appearance="secondary">
+												<Button variant="secondary">
 													{t("settings:codeIndex.clearIndexDataButton")}
-												</VSCodeButton>
+												</Button>
 											</AlertDialogTrigger>
 											<AlertDialogContent>
 												<AlertDialogHeader>
@@ -1426,13 +1427,13 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
 									)}
 							</div>
 
-							<VSCodeButton
+							<Button
 								onClick={handleSaveSettings}
 								disabled={!hasUnsavedChanges || saveStatus === "saving"}>
 								{saveStatus === "saving"
 									? t("settings:codeIndex.saving")
 									: t("settings:codeIndex.saveSettings")}
-							</VSCodeButton>
+							</Button>
 						</div>
 
 						{/* Save Status Messages */}

+ 2 - 4
webview-ui/src/components/chat/FollowUpSuggest.tsx

@@ -108,9 +108,7 @@ export const FollowUpSuggest = ({
 				const isFirstSuggestion = index === 0
 
 				return (
-					<div
-						key={`${suggestion.answer}-${ts}`}
-						className="bg-vscode-editor-background rounded-sm w-full relative group">
+					<div key={`${suggestion.answer}-${ts}`} className="w-full relative group">
 						<Button
 							variant="outline"
 							className="text-left whitespace-normal break-words w-full h-auto px-3 py-2 justify-start pr-8"
@@ -133,7 +131,7 @@ export const FollowUpSuggest = ({
 						)}
 						<StandardTooltip content={t("chat:followUpSuggest.copyToInput")}>
 							<div
-								className="absolute cursor-pointer top-2 right-3 opacity-0 group-hover:opacity-100 transition-opacity"
+								className="absolute cursor-pointer top-1.5 right-3 opacity-0 group-hover:opacity-100 transition-opacity"
 								onClick={(e) => {
 									e.stopPropagation()
 									// Cancel the auto-approve timer when edit button is clicked

+ 1 - 1
webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx

@@ -135,7 +135,7 @@ export const CheckpointMenu = ({ ts, commitHash, checkpoint, onOpenChange }: Che
 							) : (
 								<>
 									<Button
-										variant="default"
+										variant="primary"
 										onClick={onRestore}
 										className="grow"
 										data-testid="confirm-restore-btn">

+ 7 - 13
webview-ui/src/components/cloud/CloudView.tsx

@@ -1,5 +1,5 @@
 import { useEffect, useRef, useState } from "react"
-import { VSCodeButton, VSCodeProgressRing, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
+import { VSCodeProgressRing, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
 
 import { type CloudUserInfo, type CloudOrganizationMembership, TelemetryEventName } from "@roo-code/types"
 
@@ -261,18 +261,12 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone, orga
 						</div>
 
 						<div className="flex flex-col gap-2 mt-4 pl-4">
-							<VSCodeButton
-								appearance="secondary"
-								onClick={handleVisitCloudWebsite}
-								className="w-full max-w-80">
+							<Button variant="secondary" onClick={handleVisitCloudWebsite} className="w-full max-w-80">
 								{t("cloud:visitCloudWebsite")}
-							</VSCodeButton>
-							<VSCodeButton
-								appearance="secondary"
-								onClick={handleLogoutClick}
-								className="w-full max-w-80">
+							</Button>
+							<Button variant="secondary" onClick={handleLogoutClick} className="w-full max-w-80">
 								{t("cloud:logOut")}
-							</VSCodeButton>
+							</Button>
 						</div>
 					</>
 				) : (
@@ -281,9 +275,9 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone, orga
 							<div className={cn(authInProgress && "opacity-50")}>{renderCloudBenefitsContent(t)}</div>
 
 							{!authInProgress && (
-								<VSCodeButton appearance="primary" onClick={handleConnectClick} className="w-full">
+								<Button variant="primary" onClick={handleConnectClick} className="w-full">
 									{t("cloud:connect")}
-								</VSCodeButton>
+								</Button>
 							)}
 
 							{/* Manual entry section */}

+ 19 - 11
webview-ui/src/components/common/VSCodeButtonLink.tsx

@@ -1,19 +1,27 @@
 import React from "react"
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
+import { Button } from "@src/components/ui"
 
 interface VSCodeButtonLinkProps {
 	href: string
 	children: React.ReactNode
+	appearance?: "primary" | "secondary"
 	[key: string]: any
 }
 
-export const VSCodeButtonLink = ({ href, children, ...props }: VSCodeButtonLinkProps) => (
-	<a
-		href={href}
-		style={{
-			textDecoration: "none",
-			color: "inherit",
-		}}>
-		<VSCodeButton {...props}>{children}</VSCodeButton>
-	</a>
-)
+export const VSCodeButtonLink = ({ href, children, appearance, ...props }: VSCodeButtonLinkProps) => {
+	// Map appearance to variant for the new Button component
+	const variant = appearance === "primary" ? "primary" : appearance === "secondary" ? "secondary" : undefined
+
+	return (
+		<a
+			href={href}
+			style={{
+				textDecoration: "none",
+				color: "inherit",
+			}}>
+			<Button variant={variant} {...props}>
+				{children}
+			</Button>
+		</a>
+	)
+}

+ 1 - 1
webview-ui/src/components/common/VersionIndicator.tsx

@@ -13,7 +13,7 @@ const VersionIndicator: React.FC<VersionIndicatorProps> = ({ onClick, className
 	return (
 		<button
 			onClick={onClick}
-			className={`text-xs text-vscode-descriptionForeground hover:text-vscode-foreground transition-colors cursor-pointer px-2 py-1 rounded border ${className}`}
+			className={`text-xs text-vscode-descriptionForeground rounded-full hover:text-vscode-foreground transition-colors cursor-pointer px-2 py-1 border ${className}`}
 			aria-label={t("chat:versionIndicator.ariaLabel", { version: Package.version })}>
 			v{Package.version}
 		</button>

+ 2 - 2
webview-ui/src/components/history/HistoryView.tsx

@@ -91,7 +91,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 									: `${t("history:enterSelectionMode")}`
 							}>
 							<Button
-								variant={isSelectionMode ? "default" : "secondary"}
+								variant={isSelectionMode ? "primary" : "secondary"}
 								onClick={toggleSelectionMode}
 								data-testid="toggle-selection-mode-button">
 								<span
@@ -259,7 +259,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 						<Button variant="secondary" onClick={() => setSelectedTaskIds([])}>
 							{t("history:clearSelection")}
 						</Button>
-						<Button variant="default" onClick={handleBatchDelete}>
+						<Button variant="primary" onClick={handleBatchDelete}>
 							{t("history:deleteSelected")}
 						</Button>
 					</div>

+ 1 - 1
webview-ui/src/components/marketplace/MarketplaceView.tsx

@@ -103,7 +103,7 @@ export function MarketplaceView({ stateManager, onDone, targetTab }: Marketplace
 						<h3 className="font-bold m-0">{t("marketplace:title")}</h3>
 						<div className="flex gap-2 items-center">
 							<Button
-								variant="default"
+								variant="primary"
 								onClick={() => {
 									onDone?.()
 								}}>

+ 1 - 1
webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx

@@ -136,7 +136,7 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({ item,
 							/* Single Install button when not installed */
 							<Button
 								size="sm"
-								variant="default"
+								variant="primary"
 								className="text-xs h-5 py-0 px-2"
 								onClick={handleInstallClick}>
 								{t("marketplace:items.card.install")}

+ 4 - 5
webview-ui/src/components/mcp/McpView.tsx

@@ -1,7 +1,6 @@
 import React, { useState } from "react"
 import { Trans } from "react-i18next"
 import {
-	VSCodeButton,
 	VSCodeCheckbox,
 	VSCodeLink,
 	VSCodePanels,
@@ -529,15 +528,15 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
 										</React.Fragment>
 									))}
 							</div>
-							<VSCodeButton
-								appearance="secondary"
+							<Button
+								variant="secondary"
 								onClick={handleRestart}
 								disabled={server.status === "connecting"}
 								style={{ width: "calc(100% - 20px)", margin: "0 10px 10px 10px" }}>
 								{server.status === "connecting"
 									? t("mcp:serverStatus.retrying")
 									: t("mcp:serverStatus.retryConnection")}
-							</VSCodeButton>
+							</Button>
 						</div>
 					)}
 
@@ -554,7 +553,7 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
 						<Button variant="secondary" onClick={() => setShowDeleteConfirm(false)}>
 							{t("mcp:deleteDialog.cancel")}
 						</Button>
-						<Button variant="default" onClick={handleDelete}>
+						<Button variant="primary" onClick={handleDelete}>
 							{t("mcp:deleteDialog.delete")}
 						</Button>
 					</DialogFooter>

+ 5 - 5
webview-ui/src/components/modes/ModesView.tsx

@@ -1228,7 +1228,7 @@ const ModesView = ({ onDone }: ModesViewProps) => {
 				<div className="pb-4 border-b border-vscode-input-border">
 					<div className="flex gap-2 mb-4">
 						<Button
-							variant="default"
+							variant="primary"
 							onClick={() => {
 								const currentMode = getCurrentMode()
 								if (currentMode) {
@@ -1265,7 +1265,7 @@ const ModesView = ({ onDone }: ModesViewProps) => {
 						{/* Export button - visible when any mode is selected */}
 						{getCurrentMode() && (
 							<Button
-								variant="default"
+								variant="primary"
 								onClick={() => {
 									const currentMode = getCurrentMode()
 									if (currentMode?.slug && !isExporting) {
@@ -1285,7 +1285,7 @@ const ModesView = ({ onDone }: ModesViewProps) => {
 						)}
 						{/* Import button - always visible */}
 						<Button
-							variant="default"
+							variant="primary"
 							onClick={() => setShowImportDialog(true)}
 							disabled={isImporting}
 							title={t("prompts:modes.importMode")}
@@ -1605,7 +1605,7 @@ const ModesView = ({ onDone }: ModesViewProps) => {
 							<Button variant="secondary" onClick={() => setIsCreateModeDialogOpen(false)}>
 								{t("prompts:createModeDialog.buttons.cancel")}
 							</Button>
-							<Button variant="default" onClick={handleCreateMode}>
+							<Button variant="primary" onClick={handleCreateMode}>
 								{t("prompts:createModeDialog.buttons.create")}
 							</Button>
 						</div>
@@ -1682,7 +1682,7 @@ const ModesView = ({ onDone }: ModesViewProps) => {
 								{t("prompts:createModeDialog.buttons.cancel")}
 							</Button>
 							<Button
-								variant="default"
+								variant="primary"
 								onClick={() => {
 									if (!isImporting) {
 										const selectedLevel = (

+ 1 - 1
webview-ui/src/components/settings/ApiConfigManager.tsx

@@ -342,7 +342,7 @@ const ApiConfigManager = ({
 							{t("settings:common.cancel")}
 						</Button>
 						<Button
-							variant="default"
+							variant="primary"
 							disabled={!newProfileName.trim()}
 							onClick={handleNewProfileSave}
 							data-testid="create-profile-button">

+ 2 - 5
webview-ui/src/components/settings/AutoApproveToggle.tsx

@@ -113,15 +113,12 @@ export const AutoApproveToggle = ({ onToggle, ...props }: AutoApproveToggleProps
 			{Object.values(autoApproveSettingsConfig).map(({ key, descriptionKey, labelKey, icon, testId }) => (
 				<StandardTooltip key={key} content={t(descriptionKey || "")}>
 					<Button
-						variant={props[key] ? "default" : "outline"}
+						variant={props[key] ? "primary" : "secondary"}
 						onClick={() => onToggle(key, !props[key])}
 						aria-label={t(labelKey)}
 						aria-pressed={!!props[key]}
 						data-testid={testId}
-						className={cn(
-							"h-7 px-2 rounded-md flex items-center gap-1.5 text-xs whitespace-nowrap",
-							!props[key] && "opacity-50",
-						)}>
+						className={cn("gap-1.5 text-xs whitespace-nowrap", !props[key] && "opacity-50")}>
 						<span className={`codicon codicon-${icon} text-sm`} />
 						<span>{t(labelKey)}</span>
 					</Button>

+ 13 - 4
webview-ui/src/components/settings/BrowserSettings.tsx

@@ -1,9 +1,18 @@
-import { VSCodeButton, VSCodeCheckbox, VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+import { VSCodeCheckbox, VSCodeTextField, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
 import { SquareMousePointer } from "lucide-react"
 import { HTMLAttributes, useEffect, useMemo, useState } from "react"
 import { Trans } from "react-i18next"
 
-import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, Slider } from "@/components/ui"
+import {
+	Select,
+	SelectContent,
+	SelectGroup,
+	SelectItem,
+	SelectTrigger,
+	SelectValue,
+	Slider,
+	Button,
+} from "@/components/ui"
 import { useAppTranslation } from "@/i18n/TranslationContext"
 import { vscode } from "@/utils/vscode"
 import { buildDocLink } from "@src/utils/docLinks"
@@ -198,11 +207,11 @@ export const BrowserSettings = ({
 										placeholder={t("settings:browser.remote.urlPlaceholder")}
 										style={{ flexGrow: 1 }}
 									/>
-									<VSCodeButton disabled={testingConnection} onClick={testConnection}>
+									<Button disabled={testingConnection} onClick={testConnection}>
 										{testingConnection || discovering
 											? t("settings:browser.remote.testingButton")
 											: t("settings:browser.remote.testButton")}
-									</VSCodeButton>
+									</Button>
 								</div>
 								{testResult && (
 									<div

+ 1 - 1
webview-ui/src/components/settings/PromptsSettings.tsx

@@ -289,7 +289,7 @@ const PromptsSettings = ({
 										/>
 										<div className="mt-2 flex justify-start items-center gap-2">
 											<Button
-												variant="default"
+												variant="primary"
 												onClick={handleTestEnhancement}
 												disabled={isEnhancing}>
 												{t("prompts:supportPrompts.enhance.previewButton")}

+ 1 - 1
webview-ui/src/components/settings/SettingsView.tsx

@@ -548,7 +548,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
 									: t("settings:header.nothingChangedTooltip")
 						}>
 						<Button
-							variant={isSettingValid ? "default" : "secondary"}
+							variant={isSettingValid ? "primary" : "secondary"}
 							className={!isSettingValid ? "!border-vscode-errorForeground" : ""}
 							onClick={handleSubmit}
 							disabled={!isChangeDetected || !isSettingValid}

+ 4 - 5
webview-ui/src/components/settings/providers/Roo.tsx

@@ -1,11 +1,10 @@
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
-
 import { type ProviderSettings, type OrganizationAllowList, rooDefaultModelId } from "@roo-code/types"
 
 import type { RouterModels } from "@roo/api"
 
 import { useAppTranslation } from "@src/i18n/TranslationContext"
 import { vscode } from "@src/utils/vscode"
+import { Button } from "@src/components/ui"
 
 import { ModelPicker } from "../ModelPicker"
 
@@ -36,12 +35,12 @@ export const Roo = ({
 				</div>
 			) : (
 				<div className="flex flex-col gap-2">
-					<VSCodeButton
-						appearance="primary"
+					<Button
+						variant="primary"
 						onClick={() => vscode.postMessage({ type: "rooCloudSignIn" })}
 						className="w-fit">
 						{t("settings:providers.roo.connectButton")}
-					</VSCodeButton>
+					</Button>
 				</div>
 			)}
 			<ModelPicker

+ 6 - 1
webview-ui/src/components/settings/providers/__tests__/HuggingFace.spec.tsx

@@ -76,6 +76,11 @@ vi.mock("@src/i18n/TranslationContext", () => ({
 
 // Mock the UI components
 vi.mock("@src/components/ui", () => ({
+	Button: ({ children, onClick, variant, ...rest }: any) => (
+		<button onClick={onClick} data-testid="button" data-variant={variant} {...rest}>
+			{children}
+		</button>
+	),
 	Select: ({ children }: any) => <div data-testid="select">{children}</div>,
 	SelectContent: ({ children }: any) => <div data-testid="select-content">{children}</div>,
 	SelectItem: ({ children }: any) => <div data-testid="select-item">{children}</div>,
@@ -190,7 +195,7 @@ describe("HuggingFace Component", () => {
 		)
 
 		// Check that the API key button is rendered
-		const apiKeyButton = screen.getByTestId("vscode-button")
+		const apiKeyButton = screen.getByTestId("button")
 		expect(apiKeyButton).toBeInTheDocument()
 		expect(apiKeyButton).toHaveTextContent("Get Hugging Face API Key")
 	})

+ 6 - 7
webview-ui/src/components/ui/button.tsx

@@ -5,17 +5,16 @@ import { cva, type VariantProps } from "class-variance-authority"
 import { cn } from "@/lib/utils"
 
 const buttonVariants = cva(
-	"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer active:opacity-80",
+	"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-base font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer active:opacity-80",
 	{
 		variants: {
 			variant: {
-				default: "border border-vscode-input-border bg-primary text-primary-foreground hover:bg-primary/90",
+				primary: "bg-primary text-primary-foreground hover:bg-primary/70",
+				secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/70",
+				ghost: "hover:bg-accent hover:text-accent-foreground",
 				destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
 				outline:
-					"border border-vscode-input-border bg-transparent hover:bg-accent hover:text-accent-foreground",
-				secondary:
-					"border border-vscode-input-border bg-secondary text-secondary-foreground hover:bg-secondary/80",
-				ghost: "hover:bg-accent hover:text-accent-foreground",
+					"border border-foreground/50 text-secondary-foreground bg-transparent hover:bg-secondary hover:text-accent-foreground",
 				link: "text-primary underline-offset-4 hover:underline",
 				combobox:
 					"border border-vscode-dropdown-border focus-visible:border-vscode-focusBorder bg-vscode-dropdown-background hover:bg-transparent text-vscode-dropdown-foreground font-normal",
@@ -28,7 +27,7 @@ const buttonVariants = cva(
 			},
 		},
 		defaultVariants: {
-			variant: "default",
+			variant: "secondary",
 			size: "default",
 		},
 	},

+ 4 - 3
webview-ui/src/components/welcome/WelcomeView.tsx

@@ -1,7 +1,7 @@
 import { useCallback, useState, useEffect } from "react"
 import knuthShuffle from "knuth-shuffle-seeded"
 import { Trans } from "react-i18next"
-import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
 import posthog from "posthog-js"
 
 import type { ProviderSettings } from "@roo-code/types"
@@ -13,6 +13,7 @@ import { vscode } from "@src/utils/vscode"
 import { useAppTranslation } from "@src/i18n/TranslationContext"
 import { getRequestyAuthUrl, getOpenRouterAuthUrl } from "@src/oauth/urls"
 import { telemetryClient } from "@src/utils/TelemetryClient"
+import { Button } from "@src/components/ui"
 
 import ApiOptions from "../settings/ApiOptions"
 import { Tab, TabContent } from "../common/Tab"
@@ -198,9 +199,9 @@ const WelcomeView = () => {
 							{t("welcome:importSettings")}
 						</VSCodeLink>
 					</div>
-					<VSCodeButton onClick={handleSubmit} appearance="primary">
+					<Button onClick={handleSubmit} variant="primary">
 						{t("welcome:start")}
-					</VSCodeButton>
+					</Button>
 					{errorMessage && <div className="text-vscode-errorForeground">{errorMessage}</div>}
 				</div>
 			</div>