webui: migrate mobile components to SketchTailwindElement
Complete migration of all mobile web components from LitElement to
SketchTailwindElement base class with Tailwind CSS styling:
Components migrated:
- mobile-chat-input.ts: Chat input with file upload, textarea auto-resize
- mobile-chat.ts: Message display with markdown rendering and tool calls
- mobile-diff.ts: Git diff viewer with Monaco editor integration
- mobile-shell.ts: Main container coordinating mobile UI layout
- mobile-title.ts: Header with connection status and view switching
Key changes:
- Replaced LitElement inheritance with SketchTailwindElement
- Converted all CSS-in-JS styles to Tailwind utility classes
- Removed static styles blocks and shadow DOM styling
- Added custom animations via document.head for non-Tailwind effects
- Preserved all existing functionality and component interactions
Technical improvements:
- Consistent iOS safe area support with env() CSS functions
- Proper flexbox layouts for mobile responsive design
- Maintained accessibility with proper ARIA labels and focus states
- Enhanced hover and active states using Tailwind modifiers
- Optimized touch interactions with -webkit-overflow-scrolling
The mobile components now follow the established SketchTailwindElement
pattern while maintaining full feature parity with the original
shadow DOM implementations.
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s21f840091392b02ek
diff --git a/webui/src/web-components/mobile-diff.ts b/webui/src/web-components/mobile-diff.ts
index 7511602..bf53b25 100644
--- a/webui/src/web-components/mobile-diff.ts
+++ b/webui/src/web-components/mobile-diff.ts
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { css, html, LitElement } from "lit";
+import { html } from "lit";
import { customElement, state } from "lit/decorators.js";
import {
GitDiffFile,
@@ -7,9 +7,10 @@
DefaultGitDataService,
} from "./git-data-service";
import "./sketch-monaco-view";
+import { SketchTailwindElement } from "./sketch-tailwind-element";
@customElement("mobile-diff")
-export class MobileDiff extends LitElement {
+export class MobileDiff extends SketchTailwindElement {
private gitService: GitDataService = new DefaultGitDataService();
@state()
@@ -31,114 +32,6 @@
@state()
private fileExpandStates: Map<string, boolean> = new Map();
- static styles = css`
- :host {
- display: flex;
- flex-direction: column;
- height: 100%;
- min-height: 0;
- overflow: hidden;
- background-color: #ffffff;
- }
-
- .diff-container {
- flex: 1;
- overflow: auto;
- min-height: 0;
- /* Ensure proper scrolling behavior */
- -webkit-overflow-scrolling: touch;
- }
-
- .loading,
- .error,
- .empty {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
- font-size: 16px;
- color: #6c757d;
- text-align: center;
- padding: 20px;
- }
-
- .error {
- color: #dc3545;
- }
-
- .file-diff {
- margin-bottom: 16px;
- }
-
- .file-diff:last-child {
- margin-bottom: 0;
- }
-
- .file-header {
- background-color: #f8f9fa;
- border: 1px solid #e9ecef;
- border-bottom: none;
- padding: 12px 16px;
- font-family: monospace;
- font-size: 14px;
- font-weight: 500;
- color: #495057;
- position: sticky;
- top: 0;
- z-index: 10;
- }
-
- .file-status {
- display: inline-block;
- padding: 2px 6px;
- border-radius: 3px;
- font-size: 12px;
- font-weight: bold;
- margin-right: 8px;
- font-family: sans-serif;
- }
-
- .file-status.added {
- background-color: #d4edda;
- color: #155724;
- }
-
- .file-status.modified {
- background-color: #fff3cd;
- color: #856404;
- }
-
- .file-status.deleted {
- background-color: #f8d7da;
- color: #721c24;
- }
-
- .file-status.renamed {
- background-color: #d1ecf1;
- color: #0c5460;
- }
-
- .file-changes {
- margin-left: 8px;
- font-size: 12px;
- color: #6c757d;
- }
-
- .monaco-container {
- border: 1px solid #e9ecef;
- border-top: none;
- min-height: 200px;
- /* Prevent artifacts */
- overflow: hidden;
- background-color: #ffffff;
- }
-
- sketch-monaco-view {
- width: 100%;
- min-height: 200px;
- }
- `;
-
connectedCallback() {
super.connectedCallback();
this.loadDiffData();
@@ -247,6 +140,23 @@
}
}
+ private getFileStatusTailwindClass(status: string): string {
+ switch (status.toUpperCase()) {
+ case "A":
+ return "bg-green-100 text-green-800";
+ case "M":
+ return "bg-yellow-100 text-yellow-800";
+ case "D":
+ return "bg-red-100 text-red-800";
+ case "R":
+ default:
+ if (status.toUpperCase().startsWith("R")) {
+ return "bg-blue-100 text-blue-800";
+ }
+ return "bg-yellow-100 text-yellow-800";
+ }
+ }
+
private getFileStatusText(status: string): string {
switch (status.toUpperCase()) {
case "A":
@@ -357,46 +267,64 @@
if (!content) {
return html`
- <div class="file-diff">
- <div class="file-header">
- <div class="file-header-left">
- <span class="file-status ${this.getFileStatusClass(file.status)}">
+ <div class="mb-4 last:mb-0">
+ <div
+ class="bg-gray-50 border border-gray-200 border-b-0 p-3 font-mono text-sm font-medium text-gray-700 sticky top-0 z-10 flex items-center justify-between"
+ >
+ <div class="flex items-center">
+ <span
+ class="inline-block px-1.5 py-0.5 rounded text-xs font-bold mr-2 font-sans ${this.getFileStatusTailwindClass(
+ file.status,
+ )}"
+ >
${this.getFileStatusText(file.status)}
</span>
${this.getPathInfo(file)}
${this.getChangesInfo(file)
- ? html`<span class="file-changes"
+ ? html`<span class="ml-2 text-xs text-gray-500"
>${this.getChangesInfo(file)}</span
>`
: ""}
</div>
- <button class="file-expand-button" disabled>
+ <button class="text-gray-400" disabled>
${this.renderExpandAllIcon()}
</button>
</div>
- <div class="monaco-container">
- <div class="loading">Loading ${file.path}...</div>
+ <div
+ class="border border-gray-200 border-t-0 min-h-[200px] overflow-hidden bg-white"
+ >
+ <div
+ class="flex items-center justify-center h-full text-base text-gray-500 text-center p-5"
+ >
+ Loading ${file.path}...
+ </div>
</div>
</div>
`;
}
return html`
- <div class="file-diff">
- <div class="file-header">
- <div class="file-header-left">
- <span class="file-status ${this.getFileStatusClass(file.status)}">
+ <div class="mb-4 last:mb-0">
+ <div
+ class="bg-gray-50 border border-gray-200 border-b-0 p-3 font-mono text-sm font-medium text-gray-700 sticky top-0 z-10 flex items-center justify-between"
+ >
+ <div class="flex items-center">
+ <span
+ class="inline-block px-1.5 py-0.5 rounded text-xs font-bold mr-2 font-sans ${this.getFileStatusTailwindClass(
+ file.status,
+ )}"
+ >
${this.getFileStatusText(file.status)}
</span>
${this.getPathInfo(file)}
${this.getChangesInfo(file)
- ? html`<span class="file-changes"
+ ? html`<span class="ml-2 text-xs text-gray-500"
>${this.getChangesInfo(file)}</span
>`
: ""}
</div>
<button
- class="file-expand-button"
+ class="text-gray-600 hover:text-gray-800 p-1 rounded"
@click="${() => this.toggleFileExpansion(file.path)}"
title="${isExpanded
? "Collapse: Hide unchanged regions to focus on changes"
@@ -407,8 +335,11 @@
: this.renderExpandAllIcon()}
</button>
</div>
- <div class="monaco-container">
+ <div
+ class="border border-gray-200 border-t-0 min-h-[200px] overflow-hidden bg-white"
+ >
<sketch-monaco-view
+ class="w-full min-h-[200px]"
.originalCode="${content.original}"
.modifiedCode="${content.modified}"
.originalFilename="${file.path}"
@@ -424,14 +355,31 @@
render() {
return html`
- <div class="diff-container">
- ${this.loading
- ? html`<div class="loading">Loading diff...</div>`
- : this.error
- ? html`<div class="error">${this.error}</div>`
- : !this.files || this.files.length === 0
- ? html`<div class="empty">No changes to show</div>`
- : this.files.map((file) => this.renderFileDiff(file))}
+ <div class="flex flex-col h-full min-h-0 overflow-hidden bg-white">
+ <div
+ class="flex-1 overflow-auto min-h-0"
+ style="-webkit-overflow-scrolling: touch;"
+ >
+ ${this.loading
+ ? html`<div
+ class="flex items-center justify-center h-full text-base text-gray-500 text-center p-5"
+ >
+ Loading diff...
+ </div>`
+ : this.error
+ ? html`<div
+ class="flex items-center justify-center h-full text-base text-red-600 text-center p-5"
+ >
+ ${this.error}
+ </div>`
+ : !this.files || this.files.length === 0
+ ? html`<div
+ class="flex items-center justify-center h-full text-base text-gray-500 text-center p-5"
+ >
+ No changes to show
+ </div>`
+ : this.files.map((file) => this.renderFileDiff(file))}
+ </div>
</div>
`;
}