Browse Source

feat: consolidate ViewHeader and styling (#8989)

* feat: consolidate ViewHeader and styling

* feat: changeset

* fix: added back environment variables for color differentiation

* fix: co-pilot fixes

* feat: github copilot fix
Jose R. Perez 2 months ago
parent
commit
d19a877

+ 5 - 0
.changeset/six-clubs-double.md

@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+Unify ViewHeader Styles Across All Views

+ 16 - 25
webview-ui/src/components/account/AccountView.tsx

@@ -9,9 +9,8 @@ import { useInterval } from "react-use"
 import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
 import { type ClineUser, handleSignOut } from "@/context/ClineAuthContext"
 import { useExtensionState } from "@/context/ExtensionStateContext"
-import { cn } from "@/lib/utils"
 import { AccountServiceClient } from "@/services/grpc-client"
-import { getClineEnvironmentClassname } from "@/utils/environmentColors"
+import ViewHeader from "../common/ViewHeader"
 import VSCodeButtonLink from "../common/VSCodeButtonLink"
 import { updateSetting } from "../settings/utils/settingsHandlers"
 import { AccountWelcomeView } from "./AccountWelcomeView"
@@ -45,30 +44,22 @@ const ClineEnvOptions = ["Production", "Staging", "Local"] as const
 
 const AccountView = ({ onDone, clineUser, organizations, activeOrganization }: AccountViewProps) => {
 	const { environment } = useExtensionState()
-	const titleColor = getClineEnvironmentClassname(environment)
 
 	return (
-		<div className="fixed inset-0 flex flex-col overflow-hidden pt-[10px] pl-[20px]">
-			<div className="flex justify-between items-center mb-[17px] pr-[17px]">
-				<h3 className={cn("text-(--vscode-foreground) m-0", titleColor)}>
-					Account {environment !== "production" ? ` - ${environment} environment` : ""}
-				</h3>
-				<VSCodeButton onClick={onDone}>Done</VSCodeButton>
-			</div>
-			<div className="grow overflow-hidden pr-[8px] flex flex-col">
-				<div className="h-full mb-1.5">
-					{clineUser?.uid ? (
-						<ClineAccountView
-							activeOrganization={activeOrganization}
-							clineEnv={environment === "local" ? "Local" : environment === "staging" ? "Staging" : "Production"}
-							clineUser={clineUser}
-							key={clineUser.uid}
-							userOrganizations={organizations}
-						/>
-					) : (
-						<AccountWelcomeView />
-					)}
-				</div>
+		<div className="fixed inset-0 flex flex-col overflow-hidden">
+			<ViewHeader environment={environment} onDone={onDone} showEnvironmentSuffix title="Account" />
+			<div className="grow flex flex-col px-5 overflow-y-auto">
+				{clineUser?.uid ? (
+					<ClineAccountView
+						activeOrganization={activeOrganization}
+						clineEnv={environment === "local" ? "Local" : environment === "staging" ? "Staging" : "Production"}
+						clineUser={clineUser}
+						key={clineUser.uid}
+						userOrganizations={organizations}
+					/>
+				) : (
+					<AccountWelcomeView />
+				)}
 			</div>
 		</div>
 	)
@@ -312,7 +303,7 @@ export const ClineAccountView = ({ clineUser, userOrganizations, activeOrganizat
 
 	return (
 		<div className="h-full flex flex-col">
-			<div className="flex flex-col pr-3 h-full">
+			<div className="flex flex-col h-full">
 				<div className="flex flex-col w-full gap-1 mb-6">
 					<div className="flex items-center flex-wrap gap-y-4">
 						{/* {user.photoUrl ? (

+ 1 - 1
webview-ui/src/components/account/AccountWelcomeView.tsx

@@ -11,7 +11,7 @@ export const AccountWelcomeView = () => {
 	const { isLoginLoading, handleSignIn } = useClineSignIn()
 
 	return (
-		<div className="flex flex-col items-center pr-3 gap-2.5">
+		<div className="flex flex-col items-center gap-2.5">
 			<ClineLogoVariable className="size-16 mb-4" environment={environment} />
 
 			<p>

+ 43 - 0
webview-ui/src/components/common/ViewHeader.tsx

@@ -0,0 +1,43 @@
+import { Button } from "@/components/ui/button"
+import { getEnvironmentColor } from "@/utils/environmentColors"
+import type { Environment } from "../../../../src/shared/config-types"
+
+const ENV_DISPLAY_NAMES: Record<Environment, string> = {
+	production: "Production",
+	staging: "Staging",
+	local: "Local",
+	selfHosted: "Self-hosted",
+}
+
+type ViewHeaderProps = {
+	title: string
+	onDone: () => void
+	showEnvironmentSuffix?: boolean
+	environment?: Environment
+}
+
+const ViewHeader = ({ title, onDone, showEnvironmentSuffix, environment }: ViewHeaderProps) => {
+	const showSubtext = showEnvironmentSuffix && environment && environment !== "production"
+	const capitalizedEnv = environment ? ENV_DISPLAY_NAMES[environment] : ""
+	const titleColor = getEnvironmentColor(environment)
+
+	return (
+		<div className="flex justify-between items-center py-2.5 px-5 mb-[17px]">
+			<div className="relative">
+				<h3 className="m-0 text-lg font-normal" style={{ color: titleColor }}>
+					{title}
+				</h3>
+				{showSubtext && (
+					<span className="absolute left-0 top-8 -translate-y-1 text-xs text-description whitespace-nowrap">
+						{capitalizedEnv} environment
+					</span>
+				)}
+			</div>
+			<Button size="header" onClick={onDone}>
+				Done
+			</Button>
+		</div>
+	)
+}
+
+export default ViewHeader

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

@@ -9,8 +9,8 @@ import { Button } from "@/components/ui/button"
 import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select"
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { TaskServiceClient } from "@/services/grpc-client"
-import { getEnvironmentColor } from "@/utils/environmentColors"
 import { formatSize } from "@/utils/format"
+import ViewHeader from "../common/ViewHeader"
 import HistoryViewItem from "./HistoryViewItem"
 
 type HistoryViewProps = {
@@ -292,16 +292,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
 	return (
 		<div className="fixed overflow-hidden inset-0 flex flex-col w-full">
 			{/* HEADER */}
-			<div className="flex justify-between items-center py-2.5 px-5">
-				<h3
-					className="m-0"
-					style={{
-						color: getEnvironmentColor(environment),
-					}}>
-					History
-				</h3>
-				<Button onClick={() => onDone()}>Done</Button>
-			</div>
+			<ViewHeader environment={environment} onDone={onDone} title="History" />
 
 			{/* FILTERS */}
 			<div className="flex flex-col gap-3 px-3">

+ 2 - 18
webview-ui/src/components/mcp/configuration/McpConfigurationView.tsx

@@ -2,12 +2,11 @@ import { McpViewTab } from "@shared/mcp"
 import { EmptyRequest } from "@shared/proto/cline/common"
 import { McpServers } from "@shared/proto/cline/mcp"
 import { convertProtoMcpServersToMcpServers } from "@shared/proto-conversions/mcp/mcp-server-conversion"
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 import { useEffect, useState } from "react"
 import styled from "styled-components"
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { McpServiceClient } from "@/services/grpc-client"
-import { getEnvironmentColor } from "@/utils/environmentColors"
+import ViewHeader from "../../common/ViewHeader"
 import AddRemoteServerForm from "./tabs/add-server/AddRemoteServerForm"
 import ConfigureServersView from "./tabs/installed/ConfigureServersView"
 import McpMarketplaceView from "./tabs/marketplace/McpMarketplaceView"
@@ -75,22 +74,7 @@ const McpConfigurationView = ({ onDone, initialTab }: McpViewProps) => {
 				display: "flex",
 				flexDirection: "column",
 			}}>
-			<div
-				style={{
-					display: "flex",
-					justifyContent: "space-between",
-					alignItems: "center",
-					padding: "10px 17px 5px 20px",
-				}}>
-				<h3
-					style={{
-						color: getEnvironmentColor(environment),
-						margin: 0,
-					}}>
-					MCP Servers
-				</h3>
-				<VSCodeButton onClick={onDone}>Done</VSCodeButton>
-			</div>
+			<ViewHeader environment={environment} onDone={onDone} title="MCP Servers" />
 
 			<div style={{ flex: 1, overflow: "auto" }}>
 				{/* Tabs container */}

+ 4 - 16
webview-ui/src/components/settings/SettingsView.tsx

@@ -1,6 +1,5 @@
 import type { ExtensionMessage } from "@shared/ExtensionMessage"
 import { ResetStateRequest } from "@shared/proto/cline/state"
-import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 import {
 	CheckCheck,
 	FlaskConical,
@@ -17,8 +16,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { cn } from "@/lib/utils"
 import { StateServiceClient } from "@/services/grpc-client"
-import { getEnvironmentColor } from "@/utils/environmentColors"
-import { Tab, TabContent, TabHeader, TabList, TabTrigger } from "../common/Tab"
+import { Tab, TabContent, TabList, TabTrigger } from "../common/Tab"
+import ViewHeader from "../common/ViewHeader"
 import SectionHeader from "./SectionHeader"
 import AboutSection from "./sections/AboutSection"
 import ApiConfigurationSection from "./sections/ApiConfigurationSection"
@@ -131,7 +130,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
 		[],
 	) // Empty deps - these imports never change
 
-	const { version, environment, settingsInitialModelTab } = useExtensionState()
+	const { version, settingsInitialModelTab, environment } = useExtensionState()
 
 	const [activeTab, setActiveTab] = useState<string>(targetSection || SETTINGS_TABS[0].id)
 
@@ -240,20 +239,9 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
 		return <Component {...props} />
 	}, [activeTab, handleResetState, settingsInitialModelTab, version])
 
-	const titleColor = getEnvironmentColor(environment)
-
 	return (
 		<Tab>
-			<TabHeader className="flex justify-between items-center gap-2">
-				<div className="flex items-center gap-1">
-					<h3 className="text-md m-0" style={{ color: titleColor }}>
-						Settings
-					</h3>
-				</div>
-				<div className="flex gap-2">
-					<VSCodeButton onClick={onDone}>Done</VSCodeButton>
-				</div>
-			</TabHeader>
+			<ViewHeader environment={environment} onDone={onDone} title="Settings" />
 
 			<div className="flex flex-1 overflow-hidden">
 				<TabList

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

@@ -24,13 +24,14 @@ const buttonVariants = cva(
 					"bg-success/10 text-success border-[#176f2c] text-white hover:bg-[#197f31] hover:border-[#197f31] active:bg-[#156528] active:border-[#156528] hover:text-white",
 				danger: "bg-[#c42b2b] border-[#c42b2b]! text-white! hover:bg-[#a82424]! hover:border-[#a82424]! active:bg-[#8f1f1f]! active:border-[#8f1f1f]!",
 			},
-			size: {
-				default: "py-1.5 px-4 [&_svg]:size-3",
-				sm: "py-1 px-3 text-sm [&_svg]:size-2",
-				xs: "p-1 text-xs [&_svg]:size-2",
-				lg: "py-4 px-8 [&_svg]:size-4 font-medium",
-				icon: "px-0.5 m-0 [&_svg]:size-2",
-			},
+		size: {
+			default: "py-1.5 px-4 [&_svg]:size-3",
+			sm: "py-1 px-3 text-sm [&_svg]:size-2",
+			xs: "p-1 text-xs [&_svg]:size-2",
+			lg: "py-4 px-8 [&_svg]:size-4 font-medium",
+			icon: "px-0.5 m-0 [&_svg]:size-2",
+			header: "py-1 px-4 [&_svg]:size-2.5",
+		},
 		},
 		defaultVariants: {
 			variant: "default",