style(vscode-ide-companion/ui): improve component styling and layout

This commit is contained in:
yiliang114
2025-12-05 18:03:37 +08:00
parent 13aa4b03c7
commit ac9cb3a6d3
6 changed files with 47 additions and 30 deletions

View File

@@ -20,7 +20,7 @@ export const InterruptedMessage: React.FC<InterruptedMessageProps> = ({
style={{ style={{
width: '100%', width: '100%',
alignItems: 'flex-start', alignItems: 'flex-start',
paddingLeft: '30px', // keep alignment with other assistant messages, but no status icon paddingLeft: '10px', // keep alignment with other assistant messages, but no status icon
position: 'relative', position: 'relative',
paddingTop: '8px', paddingTop: '8px',
paddingBottom: '8px', paddingBottom: '8px',

View File

@@ -42,7 +42,8 @@ const parseTodoEntries = (textOutputs: string[]): TodoEntry[] => {
const lines = text.split(/\r?\n/); const lines = text.split(/\r?\n/);
const entries: TodoEntry[] = []; const entries: TodoEntry[] = [];
const todoRe = /^(?:\s*(?:[-*]|\d+[.)])\s*)?\[( |x|X|-)\]\s+(.*)$/; // Accept [ ], [x]/[X] and in-progress markers [-] or [*]
const todoRe = /^(?:\s*(?:[-*]|\d+[.)])\s*)?\[( |x|X|-|\*)\]\s+(.*)$/;
for (const line of lines) { for (const line of lines) {
const m = line.match(todoRe); const m = line.match(todoRe);
if (m) { if (m) {
@@ -51,7 +52,7 @@ const parseTodoEntries = (textOutputs: string[]): TodoEntry[] => {
const status: EntryStatus = const status: EntryStatus =
mark === 'x' || mark === 'X' mark === 'x' || mark === 'X'
? 'completed' ? 'completed'
: mark === '-' : mark === '-' || mark === '*'
? 'in_progress' ? 'in_progress'
: 'pending'; : 'pending';
if (title) { if (title) {

View File

@@ -42,7 +42,8 @@ const parsePlanEntries = (textOutputs: string[]): PlanEntry[] => {
const lines = text.split(/\r?\n/); const lines = text.split(/\r?\n/);
const entries: PlanEntry[] = []; const entries: PlanEntry[] = [];
const todoRe = /^(?:\s*(?:[-*]|\d+[.)])\s*)?\[( |x|X|-)\]\s+(.*)$/; // Accept [ ], [x]/[X] and in-progress markers [-] or [*]
const todoRe = /^(?:\s*(?:[-*]|\d+[.)])\s*)?\[( |x|X|-|\*)\]\s+(.*)$/;
for (const line of lines) { for (const line of lines) {
const m = line.match(todoRe); const m = line.match(todoRe);
if (m) { if (m) {
@@ -51,7 +52,7 @@ const parsePlanEntries = (textOutputs: string[]): PlanEntry[] => {
const status: EntryStatus = const status: EntryStatus =
mark === 'x' || mark === 'X' mark === 'x' || mark === 'X'
? 'completed' ? 'completed'
: mark === '-' : mark === '-' || mark === '*'
? 'in_progress' ? 'in_progress'
: 'pending'; : 'pending';
if (title) { if (title) {

View File

@@ -33,7 +33,7 @@ export const CheckboxDisplay: React.FC<CheckboxDisplayProps> = ({
// Pseudo-elements do not reliably render on <input> in Chromium (VS Code webviews), // Pseudo-elements do not reliably render on <input> in Chromium (VS Code webviews),
// which caused the missing icon. This version is font-free and uses borders. // which caused the missing icon. This version is font-free and uses borders.
const showCheck = !!checked && !indeterminate; const showCheck = !!checked && !indeterminate;
const showDash = !!indeterminate; const showInProgress = !!indeterminate;
return ( return (
<span <span
@@ -66,16 +66,18 @@ export const CheckboxDisplay: React.FC<CheckboxDisplayProps> = ({
].join(' ')} ].join(' ')}
/> />
) : null} ) : null}
{showDash ? ( {showInProgress ? (
<span <span
aria-hidden aria-hidden
className={[ className={[
'absolute block', 'absolute inline-block',
'left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2', 'left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
'w-2 h-[2px] rounded-sm', // Use a literal star; no icon font needed
'bg-[#e1c08d]', 'text-[11px] leading-none text-[#e1c08d] select-none',
].join(' ')} ].join(' ')}
/> >
*
</span>
) : null} ) : null}
</span> </span>
); );

View File

@@ -11,7 +11,7 @@
=========================== */ =========================== */
:root { :root {
/* Qwen Brand Colors */ /* Qwen Brand Colors */
--app-qwen-orange: #615fff; --app-qwen-theme: #615fff;
--app-qwen-clay-button-orange: #4f46e5; --app-qwen-clay-button-orange: #4f46e5;
--app-qwen-ivory: #f5f5ff; --app-qwen-ivory: #f5f5ff;
--app-qwen-slate: #141420; --app-qwen-slate: #141420;
@@ -46,7 +46,7 @@
--app-input-placeholder-foreground: var(--vscode-input-placeholderForeground); --app-input-placeholder-foreground: var(--vscode-input-placeholderForeground);
--app-input-secondary-background: var(--vscode-menu-background); --app-input-secondary-background: var(--vscode-menu-background);
/* Input Highlight (focus ring/border) */ /* Input Highlight (focus ring/border) */
--app-input-highlight: var(--app-qwen-orange); --app-input-highlight: var(--app-qwen-theme);
/* Code Highlighting */ /* Code Highlighting */
--app-code-background: var(--vscode-textCodeBlock-background, rgba(0, 0, 0, 0.05)); --app-code-background: var(--vscode-textCodeBlock-background, rgba(0, 0, 0, 0.05));
@@ -211,7 +211,7 @@ button {
} }
.input-field:focus { .input-field:focus {
border-color: var(--app-qwen-orange); border-color: var(--app-qwen-theme);
} }
.input-field:disabled { .input-field:disabled {
@@ -355,7 +355,7 @@ button {
=========================== */ =========================== */
.permission-request-card { .permission-request-card {
background: var(--app-input-background); background: var(--app-input-background);
border: 1px solid var(--app-qwen-orange); border: 1px solid var(--app-qwen-theme);
border-radius: var(--corner-radius-medium); border-radius: var(--corner-radius-medium);
margin: var(--app-spacing-medium) 0; margin: var(--app-spacing-medium) 0;
margin-bottom: var(--app-spacing-xlarge); margin-bottom: var(--app-spacing-xlarge);
@@ -500,18 +500,10 @@ button {
} }
.permission-option.selected { .permission-option.selected {
border-color: var(--app-qwen-orange); border-color: var(--app-qwen-theme);
background: rgba(97, 95, 255, 0.1); background: rgba(97, 95, 255, 0.1);
} }
.permission-option.allow {
/* Allow options */
}
.permission-option.reject {
/* Reject options */
}
.permission-radio { .permission-radio {
flex-shrink: 0; flex-shrink: 0;
} }
@@ -540,7 +532,7 @@ button {
.permission-option.selected .permission-option-number { .permission-option.selected .permission-option-number {
color: var(--app-qwen-ivory); color: var(--app-qwen-ivory);
background-color: var(--app-qwen-orange); background-color: var(--app-qwen-theme);
} }
.permission-always-badge { .permission-always-badge {

View File

@@ -51,7 +51,8 @@
.composer-form:focus-within { .composer-form:focus-within {
/* match existing highlight behavior */ /* match existing highlight behavior */
border-color: var(--app-input-highlight); border-color: var(--app-input-highlight);
box-shadow: 0 1px 2px color-mix(in srgb, var(--app-input-highlight), transparent 80%); box-shadow: 0 1px 2px
color-mix(in srgb, var(--app-input-highlight), transparent 80%);
} }
/* Composer: input editable area */ /* Composer: input editable area */
@@ -62,7 +63,11 @@
font-size: var(--vscode-chat-font-size, 13px); font-size: var(--vscode-chat-font-size, 13px);
color: var(--app-input-foreground); color: var(--app-input-foreground);
} }
.composer-input:empty:before { /* Show placeholder when truly empty OR when flagged as empty via data attribute.
The data attribute is needed because some browsers insert a <br> in
contentEditable, which breaks :empty matching. */
.composer-input:empty:before,
.composer-input[data-empty='true']::before {
content: attr(data-placeholder); content: attr(data-placeholder);
color: var(--app-input-placeholder-foreground); color: var(--app-input-placeholder-foreground);
pointer-events: none; pointer-events: none;
@@ -76,7 +81,7 @@
outline: none; outline: none;
} }
.composer-input:disabled, .composer-input:disabled,
.composer-input[contenteditable="false"] { .composer-input[contenteditable='false'] {
color: #999; color: #999;
cursor: not-allowed; cursor: not-allowed;
} }
@@ -105,7 +110,8 @@
filter: brightness(1.1); filter: brightness(1.1);
} }
.btn-text-compact > svg { .btn-text-compact > svg {
height: 1em; /* match font size */ height: 1em;
width: 1em;
flex-shrink: 0; flex-shrink: 0;
} }
.btn-text-compact > span { .btn-text-compact > span {
@@ -119,7 +125,9 @@
} }
@media screen and (max-width: 300px) { @media screen and (max-width: 300px) {
.btn-text-compact > svg { display: none; } .btn-text-compact > svg {
display: none;
}
} }
/* Icon-only button, compact square (26x26) */ /* Icon-only button, compact square (26x26) */
@@ -173,3 +181,16 @@
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
} }
/* ===========================
Utilities
=========================== */
@layer utilities {
/* Multi-line clamp with ellipsis (Chromium-based webview supported) */
.q-line-clamp-3 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
}