webui: convert SketchViewModeSelect to use SketchTailwindElement with Tailwind CSS
Replace LitElement shadow DOM component with SketchTailwindElement base class
to use Tailwind CSS utility classes instead of component-scoped CSS styles.
Also adds tailwind support for CSS container queries in addition to media
queries.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s0eeb80dd54594375k
diff --git a/webui/package-lock.json b/webui/package-lock.json
index 28616e5..67c7e9b 100644
--- a/webui/package-lock.json
+++ b/webui/package-lock.json
@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"@tailwindcss/cli": "^4.1.10",
+ "@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/vite": "^4.1.10",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
@@ -1920,6 +1921,14 @@
"tailwindcss": "dist/index.mjs"
}
},
+ "node_modules/@tailwindcss/container-queries": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/container-queries/-/container-queries-0.1.1.tgz",
+ "integrity": "sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==",
+ "peerDependencies": {
+ "tailwindcss": ">=3.2.0"
+ }
+ },
"node_modules/@tailwindcss/node": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
diff --git a/webui/package.json b/webui/package.json
index 91a1f77..c8f9861 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -28,6 +28,7 @@
},
"dependencies": {
"@tailwindcss/cli": "^4.1.10",
+ "@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/vite": "^4.1.10",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
diff --git a/webui/src/web-components/demo/demo-fixtures/index.ts b/webui/src/web-components/demo/demo-fixtures/index.ts
index 9a47f8e..627b257 100644
--- a/webui/src/web-components/demo/demo-fixtures/index.ts
+++ b/webui/src/web-components/demo/demo-fixtures/index.ts
@@ -24,6 +24,14 @@
heavyUsageState,
} from "./container-status";
+// View mode select
+export {
+ sampleViewModeConfigs,
+ viewModeScenarios,
+ applyViewModeConfig,
+ createViewModeTestButtons,
+} from "./view-mode-select";
+
// Common demo utilities
export const demoStyles = {
container: `
diff --git a/webui/src/web-components/demo/demo-fixtures/view-mode-select.ts b/webui/src/web-components/demo/demo-fixtures/view-mode-select.ts
new file mode 100644
index 0000000..5e2df2d
--- /dev/null
+++ b/webui/src/web-components/demo/demo-fixtures/view-mode-select.ts
@@ -0,0 +1,170 @@
+/**
+ * Shared demo fixtures for SketchViewModeSelect component
+ */
+
+/**
+ * Sample view mode configurations for different scenarios
+ */
+export const sampleViewModeConfigs = {
+ // Basic configuration with no diff stats
+ basic: {
+ activeMode: "chat" as const,
+ diffLinesAdded: 0,
+ diffLinesRemoved: 0,
+ },
+
+ // Configuration with small diff stats
+ smallDiff: {
+ activeMode: "diff2" as const,
+ diffLinesAdded: 5,
+ diffLinesRemoved: 2,
+ },
+
+ // Configuration with large diff stats
+ largeDiff: {
+ activeMode: "diff2" as const,
+ diffLinesAdded: 247,
+ diffLinesRemoved: 156,
+ },
+
+ // Configuration with terminal mode active
+ terminalActive: {
+ activeMode: "terminal" as const,
+ diffLinesAdded: 12,
+ diffLinesRemoved: 8,
+ },
+
+ // Configuration with only additions
+ additionsOnly: {
+ activeMode: "diff2" as const,
+ diffLinesAdded: 35,
+ diffLinesRemoved: 0,
+ },
+
+ // Configuration with only deletions
+ deletionsOnly: {
+ activeMode: "diff2" as const,
+ diffLinesAdded: 0,
+ diffLinesRemoved: 28,
+ },
+};
+
+/**
+ * Sample scenarios for interactive demos
+ */
+export const viewModeScenarios = [
+ {
+ name: "Fresh Start",
+ description: "No changes, chat mode active",
+ config: sampleViewModeConfigs.basic,
+ },
+ {
+ name: "Small Changes",
+ description: "Few lines changed, diff view active",
+ config: sampleViewModeConfigs.smallDiff,
+ },
+ {
+ name: "Major Refactor",
+ description: "Large number of changes across files",
+ config: sampleViewModeConfigs.largeDiff,
+ },
+ {
+ name: "Terminal Work",
+ description: "Running commands, terminal active",
+ config: sampleViewModeConfigs.terminalActive,
+ },
+ {
+ name: "New Feature",
+ description: "Only additions, new code written",
+ config: sampleViewModeConfigs.additionsOnly,
+ },
+ {
+ name: "Code Cleanup",
+ description: "Only deletions, removing unused code",
+ config: sampleViewModeConfigs.deletionsOnly,
+ },
+];
+
+/**
+ * Type for view mode configuration
+ */
+export type ViewModeConfig = {
+ activeMode: "chat" | "diff2" | "terminal";
+ diffLinesAdded: number;
+ diffLinesRemoved: number;
+};
+
+/**
+ * Helper function to apply configuration to a component
+ */
+export function applyViewModeConfig(
+ component: any,
+ config: ViewModeConfig,
+): void {
+ component.activeMode = config.activeMode;
+ component.diffLinesAdded = config.diffLinesAdded;
+ component.diffLinesRemoved = config.diffLinesRemoved;
+ component.requestUpdate();
+}
+
+/**
+ * Create a series of buttons for testing different scenarios
+ */
+export function createViewModeTestButtons(
+ component: any,
+ container: HTMLElement,
+): void {
+ const buttonContainer = document.createElement("div");
+ buttonContainer.style.cssText = `
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin: 15px 0;
+ padding: 10px;
+ background: #f6f8fa;
+ border-radius: 6px;
+ `;
+
+ viewModeScenarios.forEach((scenario) => {
+ const button = document.createElement("button");
+ button.textContent = scenario.name;
+ button.title = scenario.description;
+ button.style.cssText = `
+ padding: 6px 12px;
+ background: #0969da;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 12px;
+ transition: background-color 0.2s;
+ `;
+
+ button.addEventListener("mouseenter", () => {
+ button.style.backgroundColor = "#0860ca";
+ });
+
+ button.addEventListener("mouseleave", () => {
+ button.style.backgroundColor = "#0969da";
+ });
+
+ button.addEventListener("click", () => {
+ applyViewModeConfig(component, scenario.config);
+
+ // Visual feedback
+ const allButtons = buttonContainer.querySelectorAll("button");
+ allButtons.forEach((btn) => {
+ (btn as HTMLButtonElement).style.backgroundColor = "#0969da";
+ });
+ button.style.backgroundColor = "#2da44e";
+
+ setTimeout(() => {
+ button.style.backgroundColor = "#0969da";
+ }, 1000);
+ });
+
+ buttonContainer.appendChild(button);
+ });
+
+ container.appendChild(buttonContainer);
+}
diff --git a/webui/src/web-components/demo/demo-framework/demo-runner.ts b/webui/src/web-components/demo/demo-framework/demo-runner.ts
index 53f9fba..76b7085 100644
--- a/webui/src/web-components/demo/demo-framework/demo-runner.ts
+++ b/webui/src/web-components/demo/demo-framework/demo-runner.ts
@@ -97,6 +97,7 @@
"sketch-chat-input",
"sketch-container-status",
"sketch-tool-calls",
+ "sketch-view-mode-select",
];
// Filter to only components that actually have demo files
diff --git a/webui/src/web-components/demo/sketch-view-mode-select.demo.ts b/webui/src/web-components/demo/sketch-view-mode-select.demo.ts
new file mode 100644
index 0000000..c187fbd
--- /dev/null
+++ b/webui/src/web-components/demo/sketch-view-mode-select.demo.ts
@@ -0,0 +1,376 @@
+/**
+ * Demo module for sketch-view-mode-select component
+ */
+
+import { DemoModule } from "./demo-framework/types";
+import {
+ demoUtils,
+ sampleViewModeConfigs,
+ viewModeScenarios,
+ applyViewModeConfig,
+ createViewModeTestButtons,
+} from "./demo-fixtures/index";
+
+const demo: DemoModule = {
+ title: "View Mode Select Demo",
+ description:
+ "Interactive tab navigation for switching between chat, diff, and terminal views",
+ imports: ["../sketch-view-mode-select"],
+ styles: ["/dist/tailwind.css"],
+
+ setup: async (container: HTMLElement) => {
+ // Create demo sections
+ const basicSection = demoUtils.createDemoSection(
+ "Basic View Mode Selector",
+ "Click buttons to switch between different views",
+ );
+
+ const scenariosSection = demoUtils.createDemoSection(
+ "Different Scenarios",
+ "Pre-configured scenarios showing various diff stats and active modes",
+ );
+
+ const interactiveSection = demoUtils.createDemoSection(
+ "Interactive Testing",
+ "Test different configurations and watch the component update",
+ );
+
+ // Basic view mode selector
+ const basicContainer = document.createElement("div");
+ basicContainer.className = "@container";
+
+ const basicSelector = document.createElement(
+ "sketch-view-mode-select",
+ ) as any;
+ basicSelector.id = "basic-selector";
+ applyViewModeConfig(basicSelector, sampleViewModeConfigs.basic);
+
+ basicContainer.appendChild(basicSelector);
+
+ // Status display for basic selector
+ const basicStatus = document.createElement("div");
+ basicStatus.id = "basic-status";
+ basicStatus.style.cssText = `
+ margin-top: 15px;
+ padding: 10px;
+ background: #f6f8fa;
+ border-radius: 6px;
+ font-family: monospace;
+ font-size: 14px;
+ `;
+
+ const updateBasicStatus = () => {
+ basicStatus.innerHTML = `
+ <strong>Current State:</strong><br>
+ Active Mode: <code>${basicSelector.activeMode}</code><br>
+ Diff Stats: <code>+${basicSelector.diffLinesAdded} -${basicSelector.diffLinesRemoved}</code>
+ `;
+ };
+ updateBasicStatus();
+
+ // Listen for view mode changes
+ basicSelector.addEventListener("view-mode-select", (event: CustomEvent) => {
+ console.log("View mode changed:", event.detail);
+ basicSelector.activeMode = event.detail.mode;
+ updateBasicStatus();
+ });
+
+ // Create scenario examples
+ const scenarioContainer = document.createElement("div");
+ scenarioContainer.style.cssText = `
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 15px;
+ margin-top: 15px;
+ `;
+
+ viewModeScenarios.forEach((scenario) => {
+ const scenarioCard = document.createElement("div");
+ scenarioCard.style.cssText = `
+ padding: 15px;
+ border: 1px solid #d0d7de;
+ border-radius: 8px;
+ background: white;
+ `;
+
+ const scenarioTitle = document.createElement("h4");
+ scenarioTitle.textContent = scenario.name;
+ scenarioTitle.style.cssText = "margin: 0 0 5px 0; color: #24292f;";
+
+ const scenarioDesc = document.createElement("p");
+ scenarioDesc.textContent = scenario.description;
+ scenarioDesc.style.cssText =
+ "margin: 0 0 10px 0; color: #656d76; font-size: 14px;";
+
+ const scenarioWrapper = document.createElement("div");
+ scenarioWrapper.className = "@container";
+
+ const scenarioSelector = document.createElement(
+ "sketch-view-mode-select",
+ ) as any;
+ applyViewModeConfig(scenarioSelector, scenario.config);
+
+ scenarioWrapper.appendChild(scenarioSelector);
+
+ scenarioCard.appendChild(scenarioTitle);
+ scenarioCard.appendChild(scenarioDesc);
+ scenarioCard.appendChild(scenarioWrapper);
+ scenarioContainer.appendChild(scenarioCard);
+ });
+
+ // Interactive testing component
+ const interactiveSelectorContainer = document.createElement("div");
+ interactiveSelectorContainer.className = "@container";
+
+ const interactiveSelector = document.createElement(
+ "sketch-view-mode-select",
+ ) as any;
+ interactiveSelector.id = "interactive-selector";
+ applyViewModeConfig(interactiveSelector, sampleViewModeConfigs.basic);
+
+ interactiveSelectorContainer.appendChild(interactiveSelector);
+
+ // Status display for interactive selector
+ const interactiveStatus = document.createElement("div");
+ interactiveStatus.id = "interactive-status";
+ interactiveStatus.style.cssText = basicStatus.style.cssText;
+
+ const updateInteractiveStatus = () => {
+ interactiveStatus.innerHTML = `
+ <strong>Interactive Component State:</strong><br>
+ Active Mode: <code>${interactiveSelector.activeMode}</code><br>
+ Diff Lines Added: <code>${interactiveSelector.diffLinesAdded}</code><br>
+ Diff Lines Removed: <code>${interactiveSelector.diffLinesRemoved}</code><br>
+ <em>Click scenario buttons above to test different configurations</em>
+ `;
+ };
+ updateInteractiveStatus();
+
+ // Listen for view mode changes on interactive selector
+ interactiveSelector.addEventListener(
+ "view-mode-select",
+ (event: CustomEvent) => {
+ console.log("Interactive view mode changed:", event.detail);
+ interactiveSelector.activeMode = event.detail.mode;
+ updateInteractiveStatus();
+ },
+ );
+
+ // Custom controls for interactive testing
+ const customControls = document.createElement("div");
+ customControls.style.cssText = `
+ margin: 15px 0;
+ padding: 15px;
+ background: #f6f8fa;
+ border-radius: 6px;
+ `;
+
+ const addLinesButton = demoUtils.createButton("Add +5 Lines", () => {
+ interactiveSelector.diffLinesAdded += 5;
+ interactiveSelector.requestUpdate();
+ updateInteractiveStatus();
+ });
+
+ const removeLinesButton = demoUtils.createButton("Add -3 Lines", () => {
+ interactiveSelector.diffLinesRemoved += 3;
+ interactiveSelector.requestUpdate();
+ updateInteractiveStatus();
+ });
+
+ const clearDiffButton = demoUtils.createButton("Clear Diff", () => {
+ interactiveSelector.diffLinesAdded = 0;
+ interactiveSelector.diffLinesRemoved = 0;
+ interactiveSelector.requestUpdate();
+ updateInteractiveStatus();
+ });
+
+ const randomDiffButton = demoUtils.createButton("Random Diff", () => {
+ interactiveSelector.diffLinesAdded = Math.floor(Math.random() * 100) + 1;
+ interactiveSelector.diffLinesRemoved = Math.floor(Math.random() * 50) + 1;
+ interactiveSelector.requestUpdate();
+ updateInteractiveStatus();
+ });
+
+ customControls.appendChild(addLinesButton);
+ customControls.appendChild(removeLinesButton);
+ customControls.appendChild(clearDiffButton);
+ customControls.appendChild(randomDiffButton);
+
+ // Assemble the demo
+ basicSection.appendChild(basicContainer);
+ basicSection.appendChild(basicStatus);
+
+ scenariosSection.appendChild(scenarioContainer);
+
+ interactiveSection.appendChild(interactiveSelectorContainer);
+
+ // Add test buttons for interactive section
+ createViewModeTestButtons(interactiveSelector, interactiveSection);
+
+ interactiveSection.appendChild(customControls);
+ interactiveSection.appendChild(interactiveStatus);
+
+ container.appendChild(basicSection);
+ container.appendChild(scenariosSection);
+ container.appendChild(interactiveSection);
+
+ // Add container queries responsive testing section
+ const responsiveSection = demoUtils.createDemoSection(
+ "Container Query Responsive Testing",
+ "Demonstrates how the component behaves at different container sizes using Tailwind container queries",
+ );
+
+ // Create explanation text
+ const explanation = document.createElement("p");
+ explanation.style.cssText = "margin: 10px 0; color: #666; font-size: 14px;";
+ explanation.innerHTML = `
+ <strong>Container Queries:</strong> The component now uses Tailwind <code>@container</code> queries instead of viewport media queries.<br>
+ This allows different sized containers to show different responsive behaviors simultaneously.
+ `;
+ responsiveSection.appendChild(explanation);
+
+ // Create different sized container examples
+ const containerExamples = [
+ {
+ title: "Extra Wide Container (700px)",
+ description: "Shows full text labels and complete layout",
+ width: "700px",
+ config: sampleViewModeConfigs.largeDiff,
+ containerClass: "w-[700px]",
+ borderColor: "border-green-500",
+ },
+ {
+ title: "Very Narrow Container (250px)",
+ description: "Shows icons only (text hidden due to container query)",
+ width: "250px",
+ config: sampleViewModeConfigs.terminalActive,
+ containerClass: "w-[250px]",
+ borderColor: "border-orange-500",
+ },
+ ];
+
+ const examplesContainer = document.createElement("div");
+ examplesContainer.style.cssText =
+ "display: flex; flex-direction: column; gap: 20px; margin: 20px 0;";
+
+ containerExamples.forEach((example, index) => {
+ // Create container wrapper
+ const wrapper = document.createElement("div");
+ wrapper.style.cssText = `
+ border: 2px solid;
+ border-radius: 8px;
+ padding: 15px;
+ background: #f9f9f9;
+ `;
+ wrapper.className = example.borderColor;
+
+ // Create title and description
+ const header = document.createElement("div");
+ header.style.cssText = "margin-bottom: 10px;";
+ header.innerHTML = `
+ <h4 style="margin: 0 0 5px 0; font-weight: 600; color: #333;">${example.title}</h4>
+ <p style="margin: 0; font-size: 14px; color: #666;">${example.description}</p>
+ `;
+ wrapper.appendChild(header);
+
+ // Create constrained container for the component
+ const componentContainer = document.createElement("div");
+ componentContainer.className = "@container";
+ componentContainer.style.cssText = `
+ width: ${example.width};
+ border: 1px dashed #ccc;
+ padding: 10px;
+ background: white;
+ border-radius: 4px;
+ `;
+
+ // Create the component
+ const component = document.createElement(
+ "sketch-view-mode-select",
+ ) as any;
+ applyViewModeConfig(component, example.config);
+
+ componentContainer.appendChild(component);
+ wrapper.appendChild(componentContainer);
+ examplesContainer.appendChild(wrapper);
+ });
+
+ responsiveSection.appendChild(examplesContainer);
+
+ // Add interactive container size testing
+ const containerTestSection = document.createElement("div");
+ containerTestSection.style.cssText =
+ "margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd;";
+
+ const interactiveTitle = document.createElement("h4");
+ interactiveTitle.textContent = "Interactive Container Size Testing";
+ interactiveTitle.style.cssText = "margin: 0 0 10px 0; font-weight: 600;";
+
+ const interactiveDesc = document.createElement("p");
+ interactiveDesc.textContent =
+ "Use the buttons below to change the container size and see the responsive behavior in real-time.";
+ interactiveDesc.style.cssText =
+ "margin: 0 0 15px 0; color: #666; font-size: 14px;";
+
+ // Create interactive container
+ const interactiveContainer = document.createElement("div");
+ interactiveContainer.className = "@container";
+ interactiveContainer.style.cssText = `
+ border: 2px solid #007acc;
+ border-radius: 8px;
+ padding: 15px;
+ background: #f0f8ff;
+ transition: width 0.3s ease;
+ width: 700px;
+ `;
+
+ // Create interactive component
+ const interactiveComponent = document.createElement(
+ "sketch-view-mode-select",
+ ) as any;
+ applyViewModeConfig(interactiveComponent, sampleViewModeConfigs.largeDiff);
+
+ // Size info display
+ const sizeInfo = document.createElement("div");
+ sizeInfo.style.cssText =
+ "margin-bottom: 10px; font-family: monospace; font-size: 12px; color: #333;";
+ sizeInfo.textContent = "Current container width: 700px";
+
+ interactiveContainer.appendChild(sizeInfo);
+ interactiveContainer.appendChild(interactiveComponent);
+
+ // Control buttons
+ const controlButtons = document.createElement("div");
+ controlButtons.style.cssText =
+ "margin-top: 15px; display: flex; gap: 8px; flex-wrap: wrap;";
+
+ const sizes = [
+ { label: "Extra Wide (700px)", width: "700px" },
+ { label: "Medium (400px)", width: "400px" },
+ { label: "Very Narrow (250px)", width: "250px" },
+ ];
+
+ sizes.forEach((size) => {
+ const button = demoUtils.createButton(size.label, () => {
+ interactiveContainer.style.width = size.width;
+ sizeInfo.textContent = `Current container width: ${size.width}`;
+ });
+ controlButtons.appendChild(button);
+ });
+
+ containerTestSection.appendChild(interactiveTitle);
+ containerTestSection.appendChild(interactiveDesc);
+ containerTestSection.appendChild(interactiveContainer);
+ containerTestSection.appendChild(controlButtons);
+
+ responsiveSection.appendChild(containerTestSection);
+ container.appendChild(responsiveSection);
+ },
+
+ cleanup: async () => {
+ // Clean up any event listeners or intervals if needed
+ console.log("Cleaning up sketch-view-mode-select demo");
+ },
+};
+
+export default demo;
diff --git a/webui/src/web-components/sketch-view-mode-select.test.ts b/webui/src/web-components/sketch-view-mode-select.test.ts
index 58cf535..3862393 100644
--- a/webui/src/web-components/sketch-view-mode-select.test.ts
+++ b/webui/src/web-components/sketch-view-mode-select.test.ts
@@ -10,10 +10,10 @@
);
expect(activeMode).toBe("chat");
- // Check that the chat button has the active class
- await expect(
- component.locator("#showConversationButton.active"),
- ).toBeVisible();
+ // Check that the chat button has the active styling (dark blue border indicates active)
+ await expect(component.locator("#showConversationButton")).toHaveClass(
+ /border-b-blue-600/,
+ );
});
test("dispatches view-mode-select event when clicking a mode button", async ({
@@ -68,8 +68,10 @@
);
expect(activeMode).toBe("diff2");
- // Check that the diff button is now active
- await expect(component.locator("#showDiff2Button.active")).toBeVisible();
+ // Check that the diff button is now active (has dark blue border)
+ await expect(component.locator("#showDiff2Button")).toHaveClass(
+ /border-b-blue-600/,
+ );
});
test("correctly marks the active button based on mode", async ({ mount }) => {
@@ -79,13 +81,16 @@
},
});
- // Terminal button should be active
- await expect(component.locator("#showTerminalButton.active")).toBeVisible();
+ // Terminal button should be active (has dark blue border)
+ await expect(component.locator("#showTerminalButton")).toHaveClass(
+ /border-b-blue-600/,
+ );
- // Other buttons should not be active
- await expect(
- component.locator("#showConversationButton.active"),
- ).not.toBeVisible();
- await expect(component.locator("#showDiff2Button.active")).not.toBeVisible();
- await expect(component.locator("#showChartsButton.active")).not.toBeVisible();
+ // Other buttons should not be active (should not have dark blue border)
+ await expect(component.locator("#showConversationButton")).not.toHaveClass(
+ /border-b-blue-600/,
+ );
+ await expect(component.locator("#showDiff2Button")).not.toHaveClass(
+ /border-b-blue-600/,
+ );
});
diff --git a/webui/src/web-components/sketch-view-mode-select.ts b/webui/src/web-components/sketch-view-mode-select.ts
index c6385d3..0264f87 100644
--- a/webui/src/web-components/sketch-view-mode-select.ts
+++ b/webui/src/web-components/sketch-view-mode-select.ts
@@ -1,9 +1,10 @@
-import { css, html, LitElement } from "lit";
+import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import "./sketch-container-status";
+import { SketchTailwindElement } from "./sketch-tailwind-element";
@customElement("sketch-view-mode-select")
-export class SketchViewModeSelect extends LitElement {
+export class SketchViewModeSelect extends SketchTailwindElement {
// Current active mode
@property()
activeMode: "chat" | "diff2" | "terminal" = "chat";
@@ -17,81 +18,6 @@
// Header bar: view mode buttons
- static styles = css`
- /* Tab-style View Mode Styles */
- .tab-nav {
- display: flex;
- margin-right: 10px;
- background-color: #f8f8f8;
- border-radius: 4px;
- overflow: hidden;
- border: 1px solid #ddd;
- }
-
- .tab-btn {
- padding: 8px 12px;
- background: none;
- border: none;
- cursor: pointer;
- font-size: 13px;
- display: flex;
- align-items: center;
- gap: 5px;
- color: #666;
- border-bottom: 2px solid transparent;
- transition: all 0.2s ease;
- white-space: nowrap;
- }
-
- @media (max-width: 1400px) {
- .tab-btn span:not(.tab-icon):not(.diff-stats) {
- display: none;
- }
-
- .tab-btn {
- padding: 8px 10px;
- }
-
- /* Always show diff stats */
- .diff-stats {
- display: inline !important;
- font-size: 11px;
- margin-left: 2px;
- }
- }
-
- /* Style for diff stats */
- .diff-stats {
- font-size: 11px;
- margin-left: 4px;
- color: inherit;
- opacity: 0.8;
- }
-
- .tab-btn.active .diff-stats {
- opacity: 1;
- }
-
- .tab-btn:not(:last-child) {
- border-right: 1px solid #eee;
- }
-
- .tab-btn:hover {
- background-color: #f0f0f0;
- }
-
- .tab-btn.active {
- border-bottom: 2px solid #4a90e2;
- color: #4a90e2;
- font-weight: 500;
- background-color: #e6f7ff;
- }
-
- .tab-icon {
- font-size: 16px;
- }
- `;
-
constructor() {
super();
@@ -147,29 +73,41 @@
render() {
return html`
- <div class="tab-nav">
+ <div
+ class="flex mr-2.5 bg-gray-100 rounded border border-gray-300 overflow-hidden"
+ >
<button
id="showConversationButton"
- class="tab-btn ${this.activeMode === "chat" ? "active" : ""}"
+ class="px-3 py-2 bg-none border-0 border-b-2 cursor-pointer text-xs flex items-center gap-1.5 text-gray-600 border-transparent transition-all whitespace-nowrap ${this
+ .activeMode === "chat"
+ ? "!border-b-blue-600 text-blue-600 font-medium bg-blue-50"
+ : "hover:bg-gray-200"} @xl:px-3 @xl:py-2 @max-xl:px-2.5 @max-xl:[&>span:not(.tab-icon):not(.diff-stats)]:hidden @max-xl:[&>.diff-stats]:inline @max-xl:[&>.diff-stats]:text-xs @max-xl:[&>.diff-stats]:ml-0.5 border-r border-gray-200 last-of-type:border-r-0"
title="Conversation View"
@click=${() => this._handleViewModeClick("chat")}
>
- <span class="tab-icon">💬</span>
- <span>Chat</span>
+ <span class="tab-icon text-base">💬</span>
+ <span class="max-sm:hidden sm:max-xl:hidden">Chat</span>
</button>
<button
id="showDiff2Button"
- class="tab-btn ${this.activeMode === "diff2" ? "active" : ""}"
+ class="px-3 py-2 bg-none border-0 border-b-2 cursor-pointer text-xs flex items-center gap-1.5 text-gray-600 border-transparent transition-all whitespace-nowrap ${this
+ .activeMode === "diff2"
+ ? "!border-b-blue-600 text-blue-600 font-medium bg-blue-50"
+ : "hover:bg-gray-200"} @xl:px-3 @xl:py-2 @max-xl:px-2.5 @max-xl:[&>span:not(.tab-icon):not(.diff-stats)]:hidden @max-xl:[&>.diff-stats]:inline @max-xl:[&>.diff-stats]:text-xs @max-xl:[&>.diff-stats]:ml-0.5 border-r border-gray-200 last-of-type:border-r-0"
title="Diff View - ${this.diffLinesAdded > 0 ||
this.diffLinesRemoved > 0
? `+${this.diffLinesAdded} -${this.diffLinesRemoved}`
: "No changes"}"
@click=${() => this._handleViewModeClick("diff2")}
>
- <span class="tab-icon">±</span>
- <span class="diff-text">Diff</span>
+ <span class="tab-icon text-base">±</span>
+ <span class="diff-tex max-sm:hidden sm:max-xl:hidden">Diff</span>
${this.diffLinesAdded > 0 || this.diffLinesRemoved > 0
- ? html`<span class="diff-stats"
+ ? html`<span
+ class="diff-stats text-xs ml-1 opacity-80 ${this.activeMode ===
+ "diff2"
+ ? "opacity-100"
+ : ""}"
>+${this.diffLinesAdded} -${this.diffLinesRemoved}</span
>`
: ""}
@@ -177,12 +115,15 @@
<button
id="showTerminalButton"
- class="tab-btn ${this.activeMode === "terminal" ? "active" : ""}"
+ class="px-3 py-2 bg-none border-0 border-b-2 cursor-pointer text-xs flex items-center gap-1.5 text-gray-600 border-transparent transition-all whitespace-nowrap ${this
+ .activeMode === "terminal"
+ ? "!border-b-blue-600 text-blue-600 font-medium bg-blue-50"
+ : "hover:bg-gray-200"} @xl:px-3 @xl:py-2 @max-xl:px-2.5 @max-xl:[&>span:not(.tab-icon):not(.diff-stats)]:hidden @max-xl:[&>.diff-stats]:inline @max-xl:[&>.diff-stats]:text-xs @max-xl:[&>.diff-stats]:ml-0.5 border-r border-gray-200 last-of-type:border-r-0"
title="Terminal View"
@click=${() => this._handleViewModeClick("terminal")}
>
- <span class="tab-icon">💻</span>
- <span>Terminal</span>
+ <span class="tab-icon text-base">💻</span>
+ <span class="max-sm:hidden sm:max-xl:hidden">Terminal</span>
</button>
</div>
`;
diff --git a/webui/tailwind.config.js b/webui/tailwind.config.js
index 577ab81..919d100 100644
--- a/webui/tailwind.config.js
+++ b/webui/tailwind.config.js
@@ -1,8 +1,5 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,ts,jsx,tsx,html}"],
- theme: {
- extend: {},
- },
- plugins: [],
+ plugins: ["@tailwindcss/container-queries"],
};