feat(vscode-ide-companion): 更新 UI 样式

- 重构 PlanDisplay 组件和样式
- 更新 PermissionRequest 组件逻辑
- 增强 PermissionDrawer 样式,提升视觉体验

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yiliang114
2025-11-21 01:54:03 +08:00
parent 99f93b457c
commit ecc6e22002
4 changed files with 464 additions and 144 deletions

View File

@@ -154,3 +154,319 @@
}
}
/* ===========================================
Permission Request Card Styles
=========================================== */
.permission-request-card {
background: var(--app-primary-background);
border-radius: 8px;
padding: 16px;
}
.permission-card-body {
display: flex;
flex-direction: column;
gap: 16px;
}
/* Permission Header */
.permission-header {
display: flex;
align-items: flex-start;
gap: 12px;
}
.permission-icon-wrapper {
flex-shrink: 0;
}
.permission-icon {
font-size: 24px;
line-height: 1;
}
.permission-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.permission-title {
font-size: 14px;
font-weight: 500;
color: var(--app-primary-foreground);
line-height: 1.5;
word-break: break-word;
}
.permission-subtitle {
font-size: 12px;
color: var(--app-secondary-foreground);
opacity: 0.7;
}
/* Command Section - Bash style */
.permission-command-section {
display: flex;
flex-direction: column;
gap: 8px;
background: var(--app-secondary-background);
border: 1px solid var(--app-transparent-inner-border);
border-radius: 6px;
overflow: hidden;
}
.permission-command-header {
display: flex;
align-items: center;
padding: 8px 12px;
background: var(--app-secondary-background);
border-bottom: 1px solid var(--app-transparent-inner-border);
}
.permission-command-status {
display: flex;
align-items: center;
gap: 6px;
}
.permission-command-dot {
color: var(--app-qwen-orange, #ff8c00);
font-size: 8px;
line-height: 1;
}
.permission-command-label {
font-size: 11px;
font-weight: 600;
color: var(--app-secondary-foreground);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.permission-command-content {
display: flex;
flex-direction: column;
gap: 0;
}
.permission-command-input-section {
display: grid;
grid-template-columns: 32px 1fr;
align-items: flex-start;
padding: 8px 0;
background: var(--app-primary-background);
}
.permission-command-io-label {
font-size: 11px;
font-weight: 600;
color: var(--app-secondary-foreground);
opacity: 0.6;
text-align: right;
padding-right: 12px;
padding-top: 2px;
}
.permission-command-code {
font-family: var(--vscode-editor-font-family, 'Monaco', 'Courier New', monospace);
font-size: 12px;
line-height: 1.6;
color: var(--app-primary-foreground);
background: transparent;
border: none;
padding: 0 12px 0 0;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
}
.permission-command-description {
font-size: 12px;
color: var(--app-secondary-foreground);
opacity: 0.7;
padding: 8px 12px;
border-top: 1px solid var(--app-transparent-inner-border);
background: var(--app-secondary-background);
}
/* Locations Section */
.permission-locations-section {
display: flex;
flex-direction: column;
gap: 6px;
}
.permission-locations-label {
font-size: 12px;
font-weight: 500;
color: var(--app-secondary-foreground);
opacity: 0.8;
}
.permission-location-item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
background: var(--app-secondary-background);
border-radius: 4px;
font-size: 12px;
}
.permission-location-icon {
font-size: 14px;
flex-shrink: 0;
}
.permission-location-path {
flex: 1;
color: var(--app-primary-foreground);
font-family: var(--vscode-editor-font-family, 'Monaco', 'Courier New', monospace);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.permission-location-line {
color: var(--app-secondary-foreground);
opacity: 0.6;
font-family: var(--vscode-editor-font-family, 'Monaco', 'Courier New', monospace);
}
/* Options Section */
.permission-options-section {
display: flex;
flex-direction: column;
gap: 12px;
}
.permission-options-label {
font-size: 12px;
font-weight: 500;
color: var(--app-secondary-foreground);
opacity: 0.8;
}
.permission-options-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.permission-option {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
background: var(--app-secondary-background);
border: 1px solid var(--app-transparent-inner-border);
border-radius: 6px;
cursor: pointer;
transition: all 0.15s;
}
.permission-option:hover {
background: var(--app-ghost-button-hover-background);
border-color: var(--app-primary-border-color);
}
.permission-option.selected {
background: var(--app-qwen-clay-button-orange);
border-color: var(--app-qwen-orange, #ff8c00);
}
.permission-radio {
width: 16px;
height: 16px;
margin: 0;
cursor: pointer;
}
.permission-option-content {
flex: 1;
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--app-primary-foreground);
}
.permission-option-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
background: var(--app-qwen-orange, #ff8c00);
color: var(--app-qwen-ivory, #fff);
border-radius: 4px;
font-size: 11px;
font-weight: 600;
flex-shrink: 0;
}
.permission-always-badge {
font-size: 14px;
flex-shrink: 0;
}
.permission-no-options {
padding: 12px;
text-align: center;
color: var(--app-secondary-foreground);
opacity: 0.6;
font-size: 12px;
}
/* Actions */
.permission-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 8px;
}
.permission-confirm-button {
padding: 8px 16px;
background: var(--app-qwen-orange, #ff8c00);
color: var(--app-qwen-ivory, #fff);
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
}
.permission-confirm-button:hover:not(:disabled) {
opacity: 0.9;
}
.permission-confirm-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Success Message */
.permission-success {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: var(--app-qwen-green, #6BCF7F);
color: var(--app-qwen-ivory, #fff);
border-radius: 6px;
font-size: 13px;
}
.permission-success-icon {
font-size: 16px;
flex-shrink: 0;
}
.permission-success-text {
flex: 1;
}

View File

@@ -117,10 +117,25 @@ export const PermissionRequest: React.FC<PermissionRequestProps> = ({
{/* Show command if available */}
{(toolCall.rawInput?.command || toolCall.title) && (
<div className="permission-command-section">
<div className="permission-command-label">Command</div>
<code className="permission-command-code">
{toolCall.rawInput?.command || toolCall.title}
</code>
<div className="permission-command-header">
<div className="permission-command-status">
<span className="permission-command-dot"></span>
<span className="permission-command-label">COMMAND</span>
</div>
</div>
<div className="permission-command-content">
<div className="permission-command-input-section">
<span className="permission-command-io-label">IN</span>
<code className="permission-command-code">
{toolCall.rawInput?.command || toolCall.title}
</code>
</div>
{toolCall.rawInput?.description && (
<div className="permission-command-description">
{toolCall.rawInput.description}
</div>
)}
</div>
</div>
)}

View File

@@ -6,65 +6,54 @@
/**
* PlanDisplay.css - Styles for the task plan component
* Simple, clean timeline-style design
* Clean checklist-style design matching Claude Code CLI
*/
.plan-display {
background: var(--app-secondary-background);
border: 1px solid var(--app-transparent-inner-border);
border-radius: var(--corner-radius-medium);
padding: 16px;
margin: 12px 0;
animation: fadeIn 0.3s ease-in;
background: transparent;
border: none;
padding: 8px 16px;
margin: 8px 0;
}
.plan-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
color: var(--app-primary-foreground);
gap: 6px;
margin-bottom: 8px;
}
.plan-header-icon {
.plan-progress-icons {
display: flex;
align-items: center;
gap: 2px;
}
.plan-progress-icon {
flex-shrink: 0;
opacity: 0.8;
color: var(--app-secondary-foreground);
opacity: 0.6;
}
.plan-title {
font-size: 14px;
font-weight: 600;
color: var(--app-primary-foreground);
font-size: 12px;
font-weight: 400;
color: var(--app-secondary-foreground);
opacity: 0.8;
}
.plan-entries {
display: flex;
flex-direction: column;
gap: 0;
position: relative;
gap: 1px;
}
.plan-entry {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 8px 0;
position: relative;
}
/* Vertical line on the left */
.plan-entry-line {
position: absolute;
left: 7px;
top: 24px;
bottom: -8px;
width: 2px;
background: var(--app-qwen-clay-button-orange);
opacity: 0.3;
}
.plan-entry:last-child .plan-entry-line {
display: none;
align-items: center;
gap: 8px;
padding: 3px 0;
min-height: 20px;
}
/* Icon container */
@@ -73,60 +62,62 @@
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
position: relative;
z-index: 1;
width: 14px;
height: 14px;
}
.plan-icon {
display: block;
width: 14px;
height: 14px;
}
/* 不同状态的图标颜色 */
.plan-icon.pending {
color: var(--app-secondary-foreground);
opacity: 0.35;
}
.plan-icon.in-progress {
color: var(--app-secondary-foreground);
opacity: 0.7;
}
.plan-icon.completed {
color: #4caf50; /* 绿色勾号 */
opacity: 0.8;
}
/* Content */
.plan-entry-content {
flex: 1;
display: flex;
align-items: flex-start;
gap: 8px;
min-height: 24px;
padding-top: 1px;
}
.plan-entry-number {
font-size: 13px;
font-weight: 500;
color: var(--app-secondary-foreground);
flex-shrink: 0;
min-width: 24px;
align-items: center;
}
.plan-entry-text {
flex: 1;
font-size: 13px;
line-height: 1.6;
font-size: 12px;
line-height: 1.5;
color: var(--app-primary-foreground);
opacity: 0.85;
}
/* Status-specific styles */
.plan-entry.completed .plan-entry-text {
opacity: 0.6;
opacity: 0.5;
text-decoration: line-through;
}
.plan-entry.in_progress .plan-entry-text {
font-weight: 500;
}
.plan-entry.in_progress .plan-entry-number {
color: var(--app-qwen-orange);
font-weight: 600;
font-weight: 400;
opacity: 0.9;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-8px);
transform: translateY(-4px);
}
to {
opacity: 1;

View File

@@ -21,77 +21,67 @@ interface PlanDisplayProps {
* PlanDisplay component - displays AI's task plan/todo list
*/
export const PlanDisplay: React.FC<PlanDisplayProps> = ({ entries }) => {
const getStatusIcon = (status: string, _index: number) => {
// 计算完成进度
const completedCount = entries.filter((e) => e.status === 'completed').length;
const totalCount = entries.length;
const getStatusIcon = (status: string) => {
switch (status) {
case 'in_progress':
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
className="plan-icon in-progress"
>
<rect
x="2"
y="2"
width="12"
height="12"
rx="2"
fill="var(--app-qwen-orange)"
/>
<path
d="M7 4L7 12M10 8L4 8"
stroke="var(--app-qwen-ivory)"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
case 'completed':
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
className="plan-icon completed"
>
<rect
x="2"
y="2"
width="12"
height="12"
rx="2"
fill="var(--app-qwen-green, #6BCF7F)"
/>
<circle cx="7" cy="7" r="6" fill="currentColor" opacity="0.2" />
<path
d="M5 8L7 10L11 6"
stroke="var(--app-qwen-ivory)"
d="M4 7.5L6 9.5L10 4.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
default:
case 'in_progress':
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
className="plan-icon in-progress"
>
<circle
cx="7"
cy="7"
r="5"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
/>
</svg>
);
default:
// pending
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
className="plan-icon pending"
>
<rect
x="2.5"
y="2.5"
width="11"
height="11"
rx="2"
stroke="var(--app-secondary-foreground)"
<circle
cx="7"
cy="7"
r="5.5"
fill="none"
stroke="currentColor"
strokeWidth="1"
fill="transparent"
/>
</svg>
);
@@ -101,41 +91,49 @@ export const PlanDisplay: React.FC<PlanDisplayProps> = ({ entries }) => {
return (
<div className="plan-display">
<div className="plan-header">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
className="plan-header-icon"
>
<rect
x="3"
y="3"
<div className="plan-progress-icons">
<svg
width="14"
height="14"
rx="2"
stroke="currentColor"
strokeWidth="1.5"
viewBox="0 0 14 14"
fill="none"
/>
<path
d="M3 7H17M7 3V7M13 3V7"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
<span className="plan-title">Task Plan</span>
className="plan-progress-icon"
>
<circle
cx="7"
cy="7"
r="5.5"
fill="none"
stroke="currentColor"
strokeWidth="1"
/>
</svg>
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
className="plan-progress-icon"
>
<circle cx="7" cy="7" r="6" fill="currentColor" opacity="0.2" />
<path
d="M4 7.5L6 9.5L10 4.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
<span className="plan-title">
{completedCount} of {totalCount} Done
</span>
</div>
<div className="plan-entries">
{entries.map((entry, index) => (
<div key={index} className={`plan-entry ${entry.status}`}>
<div className="plan-entry-line"></div>
<div className="plan-entry-icon">
{getStatusIcon(entry.status, index)}
</div>
<div className="plan-entry-icon">{getStatusIcon(entry.status)}</div>
<div className="plan-entry-content">
<span className="plan-entry-number">{index + 1}.</span>
<span className="plan-entry-text">{entry.content}</span>
</div>
</div>