webui: migrate sketch-monaco-view to tailind
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s2b8f6e849aabb45fk
diff --git a/webui/src/web-components/demo/sketch-network-status.demo.ts b/webui/src/web-components/demo/sketch-network-status.demo.ts
deleted file mode 100644
index 2c808c7..0000000
--- a/webui/src/web-components/demo/sketch-network-status.demo.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { DemoModule } from "./demo-framework/types";
-import { demoUtils } from "./demo-fixtures/index";
-
-const demo: DemoModule = {
- title: "Sketch Network Status Demo",
- description:
- "Status indicators showing different connection and activity states",
- imports: ["../sketch-network-status.ts", "../sketch-call-status.ts"],
-
- customStyles: `
- .status-container {
- margin: 20px 0;
- padding: 10px;
- border: 1px solid #ccc;
- border-radius: 4px;
- }
- .label {
- font-weight: bold;
- margin-bottom: 5px;
- }
- `,
-
- setup: async (container: HTMLElement) => {
- const section = demoUtils.createDemoSection(
- "Status Indicators",
- "Demonstrates different connection and activity states using sketch-call-status component",
- );
-
- // Connected State
- const connectedContainer = document.createElement("div");
- connectedContainer.className = "status-container";
-
- const connectedLabel = document.createElement("div");
- connectedLabel.className = "label";
- connectedLabel.textContent = "Connected State:";
-
- const connectedStatus = document.createElement("sketch-call-status") as any;
- connectedStatus.isDisconnected = false;
- connectedStatus.isIdle = true;
- connectedStatus.llmCalls = 0;
- connectedStatus.toolCalls = [];
-
- connectedContainer.appendChild(connectedLabel);
- connectedContainer.appendChild(connectedStatus);
-
- // Working State
- const workingContainer = document.createElement("div");
- workingContainer.className = "status-container";
-
- const workingLabel = document.createElement("div");
- workingLabel.className = "label";
- workingLabel.textContent = "Working State:";
-
- const workingStatus = document.createElement("sketch-call-status") as any;
- workingStatus.isDisconnected = false;
- workingStatus.isIdle = false;
- workingStatus.llmCalls = 1;
- workingStatus.toolCalls = ["bash"];
-
- workingContainer.appendChild(workingLabel);
- workingContainer.appendChild(workingStatus);
-
- // Disconnected State
- const disconnectedContainer = document.createElement("div");
- disconnectedContainer.className = "status-container";
-
- const disconnectedLabel = document.createElement("div");
- disconnectedLabel.className = "label";
- disconnectedLabel.textContent = "Disconnected State:";
-
- const disconnectedStatus = document.createElement(
- "sketch-call-status",
- ) as any;
- disconnectedStatus.isDisconnected = true;
- disconnectedStatus.isIdle = true;
- disconnectedStatus.llmCalls = 0;
- disconnectedStatus.toolCalls = [];
-
- disconnectedContainer.appendChild(disconnectedLabel);
- disconnectedContainer.appendChild(disconnectedStatus);
-
- // Add all containers to section
- section.appendChild(connectedContainer);
- section.appendChild(workingContainer);
- section.appendChild(disconnectedContainer);
- container.appendChild(section);
- },
-};
-
-export default demo;
diff --git a/webui/src/web-components/sketch-monaco-view.ts b/webui/src/web-components/sketch-monaco-view.ts
index 2ab89fa..1e558f8 100644
--- a/webui/src/web-components/sketch-monaco-view.ts
+++ b/webui/src/web-components/sketch-monaco-view.ts
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any, no-async-promise-executor, @typescript-eslint/ban-ts-comment */
-import { css, html, LitElement } from "lit";
+import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { createRef, Ref, ref } from "lit/directives/ref.js";
+import { SketchTailwindElement } from "./sketch-tailwind-element.js";
// See https://rodydavis.com/posts/lit-monaco-editor for some ideas.
@@ -74,24 +75,24 @@
const monacoStyles = `
/* Import Monaco editor styles */
@import url('./static/monaco/min/vs/editor/editor.main.css');
-
+
/* Codicon font is now defined globally in sketch-app-shell.css */
-
+
/* Custom Monaco styles */
.monaco-editor {
width: 100%;
height: 100%;
}
-
+
/* Ensure light theme colors */
.monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input {
background-color: var(--monaco-editor-bg, #ffffff) !important;
}
-
+
.monaco-editor .margin {
background-color: var(--monaco-editor-margin, #f5f5f5) !important;
}
-
+
/* Glyph decoration styles - only show on hover */
.comment-glyph-decoration {
width: 16px !important;
@@ -100,7 +101,7 @@
opacity: 0;
transition: opacity 0.2s ease;
}
-
+
.comment-glyph-decoration:before {
content: '💬';
font-size: 12px;
@@ -110,7 +111,7 @@
display: block;
text-align: center;
}
-
+
.comment-glyph-decoration.hover-visible {
opacity: 1;
}
@@ -138,7 +139,7 @@
};
@customElement("sketch-monaco-view")
-export class CodeDiffEditor extends LitElement {
+export class CodeDiffEditor extends SketchTailwindElement {
// Editable state
@property({ type: Boolean, attribute: "editable-right" })
editableRight?: boolean;
@@ -275,192 +276,56 @@
});
}
- static styles = css`
- /* Save indicator styles */
- .save-indicator {
- position: absolute;
- top: 4px;
- right: 4px;
- padding: 3px 8px;
- border-radius: 3px;
- font-size: 12px;
- font-family: system-ui, sans-serif;
- color: white;
- z-index: 100;
- opacity: 0.9;
- pointer-events: none;
- transition: opacity 0.3s ease;
- }
-
- .save-indicator.idle {
- background-color: #6c757d;
- }
-
- .save-indicator.modified {
- background-color: #f0ad4e;
- }
-
- .save-indicator.saving {
- background-color: #5bc0de;
- }
-
- .save-indicator.saved {
- background-color: #5cb85c;
- }
-
- /* Editor host styles */
- :host {
+ render() {
+ // Set host element styles for layout (equivalent to :host styles)
+ this.style.cssText = `
--editor-width: 100%;
--editor-height: 100%;
display: flex;
- flex: none; /* Don't grow/shrink - size is determined by content */
- min-height: 0; /* Critical for flex layout */
- position: relative; /* Establish positioning context */
- width: 100%; /* Take full width */
- /* Height will be set dynamically by setupAutoSizing */
- }
- main {
- width: 100%;
- height: 100%; /* Fill the host element completely */
- border: 1px solid #e0e0e0;
- flex: none; /* Size determined by parent */
- min-height: 200px; /* Ensure a minimum height for the editor */
- /* Remove absolute positioning - use normal block layout */
+ flex: none;
+ min-height: 0;
position: relative;
- display: block;
- box-sizing: border-box; /* Include border in width calculation */
- }
-
- /* Comment box styles */
- .comment-box {
- position: fixed;
- background-color: white;
- border: 1px solid #ddd;
- border-radius: 4px;
- box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
- padding: 12px;
- z-index: 10001;
- width: 600px;
- animation: fadeIn 0.2s ease-in-out;
- max-height: 80vh;
- overflow-y: auto;
- }
-
- .comment-box-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px;
- }
-
- .comment-box-header h3 {
- margin: 0;
- font-size: 14px;
- font-weight: 500;
- }
-
- .close-button {
- background: none;
- border: none;
- cursor: pointer;
- font-size: 16px;
- color: #666;
- padding: 2px 6px;
- }
-
- .close-button:hover {
- color: #333;
- }
-
- .selected-text-preview {
- background-color: #f5f5f5;
- border: 1px solid #eee;
- border-radius: 3px;
- padding: 8px;
- margin-bottom: 10px;
- font-family: monospace;
- font-size: 12px;
- overflow-y: auto;
- white-space: pre-wrap;
- word-break: break-all;
- line-height: 1.4;
- }
-
- .selected-text-preview.small-selection {
- /* For selections of 10 lines or fewer, ensure all content is visible */
- max-height: none;
- }
-
- .selected-text-preview.large-selection {
- /* For selections larger than 10 lines, limit height with scroll */
- max-height: 280px; /* Approximately 10 lines at 12px font with 1.4 line-height */
- }
-
- .comment-textarea {
width: 100%;
- min-height: 80px;
- padding: 8px;
- border: 1px solid #ddd;
- border-radius: 3px;
- resize: vertical;
- font-family: inherit;
- margin-bottom: 10px;
- box-sizing: border-box;
- }
+ `;
- .comment-actions {
- display: flex;
- justify-content: flex-end;
- gap: 8px;
- }
-
- .comment-actions button {
- padding: 6px 12px;
- border-radius: 3px;
- cursor: pointer;
- font-size: 12px;
- }
-
- .cancel-button {
- background-color: transparent;
- border: 1px solid #ddd;
- }
-
- .cancel-button:hover {
- background-color: #f5f5f5;
- }
-
- .submit-button {
- background-color: #4285f4;
- color: white;
- border: none;
- }
-
- .submit-button:hover {
- background-color: #3367d6;
- }
-
- @keyframes fadeIn {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
- }
- `;
-
- render() {
return html`
<style>
${monacoStyles}
+
+ /* Custom animation for comment box fade-in */
+ @keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+ .animate-fade-in {
+ animation: fadeIn 0.2s ease-in-out;
+ }
</style>
- <main ${ref(this.container)}></main>
+
+ <main
+ ${ref(this.container)}
+ class="w-full h-full border border-gray-300 flex-none min-h-[200px] relative block box-border"
+ ></main>
<!-- Save indicator - shown when editing -->
${this.editableRight
? html`
- <div class="save-indicator ${this.saveState}">
+ <div
+ class="absolute top-1 right-1 px-2 py-0.5 rounded text-xs font-sans text-white z-[100] opacity-90 pointer-events-none transition-opacity duration-300 ${this
+ .saveState === "idle"
+ ? "bg-gray-500"
+ : this.saveState === "modified"
+ ? "bg-yellow-500"
+ : this.saveState === "saving"
+ ? "bg-blue-400"
+ : this.saveState === "saved"
+ ? "bg-green-500"
+ : "bg-gray-500"}"
+ >
${this.saveState === "idle"
? "Editable"
: this.saveState === "modified"
@@ -478,36 +343,48 @@
${this.showCommentBox
? html`
<div
- class="comment-box"
+ class="fixed bg-white border border-gray-300 rounded shadow-lg p-3 z-[10001] w-[600px] animate-fade-in max-h-[80vh] overflow-y-auto"
style="top: ${this.commentBoxPosition.top}px; left: ${this
.commentBoxPosition.left}px;"
>
- <div class="comment-box-header">
- <h3>Add comment</h3>
- <button class="close-button" @click="${this.closeCommentBox}">
+ <div class="flex justify-between items-center mb-2">
+ <h3 class="m-0 text-sm font-medium">Add comment</h3>
+ <button
+ class="bg-none border-none cursor-pointer text-base text-gray-600 px-1.5 py-0.5 hover:text-gray-800"
+ @click="${this.closeCommentBox}"
+ >
×
</button>
</div>
${this.selectedLines
? html`
<div
- class="selected-text-preview ${this.getPreviewCssClass()}"
+ class="bg-gray-100 border border-gray-200 rounded p-2 mb-2.5 font-mono text-xs overflow-y-auto whitespace-pre-wrap break-all leading-relaxed ${this.getPreviewCssClass() ===
+ "small-selection"
+ ? ""
+ : "max-h-[280px]"}"
>
${this.selectedLines.text}
</div>
`
: ""}
<textarea
- class="comment-textarea"
+ class="w-full min-h-[80px] p-2 border border-gray-300 rounded resize-y font-inherit mb-2.5 box-border"
placeholder="Type your comment here..."
.value="${this.commentText}"
@input="${this.handleCommentInput}"
></textarea>
- <div class="comment-actions">
- <button class="cancel-button" @click="${this.closeCommentBox}">
+ <div class="flex justify-end gap-2">
+ <button
+ class="px-3 py-1.5 rounded cursor-pointer text-xs bg-transparent border border-gray-300 hover:bg-gray-100"
+ @click="${this.closeCommentBox}"
+ >
Cancel
</button>
- <button class="submit-button" @click="${this.submitComment}">
+ <button
+ class="px-3 py-1.5 rounded cursor-pointer text-xs bg-blue-600 text-white border-none hover:bg-blue-700"
+ @click="${this.submitComment}"
+ >
Add
</button>
</div>
diff --git a/webui/src/web-components/sketch-network-status.test.ts b/webui/src/web-components/sketch-network-status.test.ts
deleted file mode 100644
index b15fbfd..0000000
--- a/webui/src/web-components/sketch-network-status.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { SketchNetworkStatus } from "./sketch-network-status";
-
-// Test that the network status component doesn't display visible content
-// since we've removed the green dot indicator
-test("network status component is not visible", async ({ mount }) => {
- const component = await mount(SketchNetworkStatus, {
- props: {
- connection: "connected",
- },
- });
-
- // The status container should exist but be hidden with display: none
- await expect(component.locator(".status-container")).toHaveCSS(
- "display",
- "none",
- );
-});
-
-// Test that the network status component remains invisible regardless of connection state
-test("network status component is not visible when disconnected", async ({
- mount,
-}) => {
- const component = await mount(SketchNetworkStatus, {
- props: {
- connection: "disconnected",
- error: "Connection error",
- },
- });
-
- // The status container should exist but be hidden with display: none
- await expect(component.locator(".status-container")).toHaveCSS(
- "display",
- "none",
- );
-});
diff --git a/webui/src/web-components/sketch-network-status.ts b/webui/src/web-components/sketch-network-status.ts
deleted file mode 100644
index f604bd5..0000000
--- a/webui/src/web-components/sketch-network-status.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property } from "lit/decorators.js";
-
-@customElement("sketch-network-status")
-export class SketchNetworkStatus extends LitElement {
- @property()
- connection: string;
-
- @property()
- error: string;
-
- // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
- // Note that these styles only apply to the scope of this web component's
- // shadow DOM node, so they won't leak out or collide with CSS declared in
- // other components or the containing web page (...unless you want it to do that).
-
- static styles = css`
- .status-container {
- display: none; /* Hide by default - we're removing the dot */
- }
- `;
-
- constructor() {
- super();
- }
-
- // See https://lit.dev/docs/components/lifecycle/
- connectedCallback() {
- super.connectedCallback();
- }
-
- // See https://lit.dev/docs/components/lifecycle/
- disconnectedCallback() {
- super.disconnectedCallback();
- }
-
- render() {
- // We no longer show any content as the dot is being removed
- // The connection status will now be handled by the call-status component
- return html` <div class="status-container"></div> `;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "sketch-network-status": SketchNetworkStatus;
- }
-}