fix: layout fixes

This commit is contained in:
2026-02-26 12:09:27 +01:00
parent 121aa6a9f7
commit a3c571f7cd
8 changed files with 481 additions and 46 deletions

View File

@@ -9,28 +9,117 @@ interface A2UIComponentProps {
renderChildren?: (children: A2UIResolvedComponent[]) => React.ReactNode;
}
interface SegmentEntry {
label: string;
value: number;
}
interface SeriesEntry {
label: string;
value: number;
segments?: SegmentEntry[];
}
const SEGMENT_COLORS = [
'var(--vscode-charts-blue, #75beff)',
'var(--vscode-charts-green, #89d185)',
'var(--vscode-charts-orange, #d18616)',
'var(--vscode-charts-red, #f14c4c)',
'var(--vscode-charts-purple, #b180d7)',
'var(--vscode-charts-yellow, #e2e210)',
];
function getSegmentColor(index: number): string {
return SEGMENT_COLORS[index % SEGMENT_COLORS.length];
}
/** Collect unique segment labels across all series entries, preserving order. */
function collectSegmentLabels(series: SeriesEntry[]): string[] {
const seen = new Set<string>();
const labels: string[] = [];
for (const entry of series) {
if (entry.segments) {
for (const seg of entry.segments) {
if (!seen.has(seg.label)) {
seen.add(seg.label);
labels.push(seg.label);
}
}
}
}
return labels;
}
export const A2UIChart: React.FC<A2UIComponentProps> = ({ component }) => {
const chartType = String(component.properties.chartType ?? 'bar');
const title = component.properties.title as string | undefined;
const series = (component.boundValue as SeriesEntry[]) ?? (component.properties.series as SeriesEntry[]) ?? [];
const maxValue = Math.max(...series.map((entry) => entry.value), 0);
const isStacked = chartType === 'stacked-bar';
const maxValue = Math.max(
...series.map((entry) => {
if (isStacked && entry.segments) {
return entry.segments.reduce((sum, s) => sum + s.value, 0);
}
return entry.value;
}),
0,
);
const segmentLabels = isStacked ? collectSegmentLabels(series) : [];
return (
<div className="assistant-panel-chart">
{title && <p className="assistant-panel-chart-title">{title}</p>}
<div className="assistant-panel-chart-type">{chartType}</div>
{series.map((entry, index) => (
<div key={`${component.id}-series-${index}`} className="assistant-panel-chart-item">
<span>{entry.label}</span>
<progress value={entry.value} max={maxValue || 1} />
<span>{entry.value}</span>
{series.map((entry, index) => {
const totalValue = isStacked && entry.segments
? entry.segments.reduce((sum, s) => sum + s.value, 0)
: entry.value;
return (
<div key={`${component.id}-series-${index}`} className="assistant-panel-chart-item">
<span className="assistant-panel-chart-label">{entry.label}</span>
<div className="assistant-panel-chart-bar-track">
{isStacked && entry.segments ? (
entry.segments.map((seg, si) => {
const segWidth = maxValue > 0 ? (seg.value / maxValue) * 100 : 0;
return (
<div
key={`${component.id}-seg-${index}-${si}`}
className="assistant-panel-chart-bar-segment"
style={{
width: `${segWidth}%`,
backgroundColor: getSegmentColor(segmentLabels.indexOf(seg.label)),
}}
title={`${seg.label}: ${seg.value}`}
/>
);
})
) : (
<div
className="assistant-panel-chart-bar-fill"
style={{ width: `${maxValue > 0 ? (entry.value / maxValue) * 100 : 0}%` }}
/>
)}
</div>
<span className="assistant-panel-chart-value">{totalValue}</span>
</div>
);
})}
{isStacked && segmentLabels.length > 0 && (
<div className="assistant-panel-chart-legend">
{segmentLabels.map((label, i) => (
<span key={label} className="assistant-panel-chart-legend-item">
<span
className="assistant-panel-chart-legend-swatch"
style={{ backgroundColor: getSegmentColor(i) }}
/>
{label}
</span>
))}
</div>
))}
)}
</div>
);
};

View File

@@ -74,7 +74,7 @@
padding-top: 10px;
}
.assistant-sidebar-metric {
.assistant-panel-metric {
display: flex;
justify-content: space-between;
align-items: baseline;
@@ -83,22 +83,22 @@
border-radius: 6px;
}
.assistant-sidebar-metric-label {
.assistant-panel-metric-label {
font-size: 12px;
opacity: 0.85;
}
.assistant-sidebar-metric-value {
.assistant-panel-metric-value {
font-size: 14px;
}
.assistant-sidebar-table {
.assistant-panel-table {
width: 100%;
border-collapse: collapse;
}
.assistant-sidebar-table th,
.assistant-sidebar-table td {
.assistant-panel-table th,
.assistant-panel-table td {
border: 1px solid var(--vscode-panel-border);
padding: 6px;
font-size: 12px;
@@ -112,30 +112,30 @@
white-space: pre-wrap;
}
.assistant-sidebar-widget-block {
.assistant-panel-widget-block {
display: flex;
flex-direction: column;
gap: 6px;
}
.assistant-sidebar-widget-label {
.assistant-panel-widget-label {
font-size: 12px;
opacity: 0.9;
}
.assistant-sidebar-widget-input {
.assistant-panel-widget-input {
width: 100%;
padding: 8px;
}
.assistant-sidebar-checkbox {
.assistant-panel-checkbox {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
}
.assistant-sidebar-chart {
.assistant-panel-chart {
display: flex;
flex-direction: column;
gap: 6px;
@@ -144,30 +144,99 @@
padding: 8px;
}
.assistant-sidebar-chart-title {
.assistant-panel-chart-title {
margin: 0;
font-weight: 600;
}
.assistant-sidebar-chart-type {
.assistant-panel-chart-type {
font-size: 11px;
text-transform: uppercase;
opacity: 0.7;
}
.assistant-sidebar-chart-item {
.assistant-panel-chart-item {
display: grid;
grid-template-columns: minmax(48px, auto) 1fr auto;
grid-template-columns: auto 1fr auto;
gap: 8px;
align-items: center;
font-size: 12px;
}
.assistant-sidebar-chart-item progress {
width: 100%;
.assistant-panel-chart-label {
justify-self: end;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 140px;
font-variant-numeric: tabular-nums;
}
.assistant-sidebar-form {
.assistant-panel-chart-bar-track {
height: 14px;
background: var(--vscode-input-background);
border-radius: 3px;
overflow: hidden;
display: flex;
min-width: 60px;
}
.assistant-panel-chart-bar-fill {
height: 100%;
background: var(--vscode-charts-blue, #75beff);
border-radius: 3px;
min-width: 2px;
transition: width 0.3s ease;
}
.assistant-panel-chart-bar-segment {
height: 100%;
min-width: 1px;
transition: width 0.3s ease;
}
.assistant-panel-chart-bar-segment:first-child {
border-radius: 3px 0 0 3px;
}
.assistant-panel-chart-bar-segment:last-child {
border-radius: 0 3px 3px 0;
}
.assistant-panel-chart-bar-segment:only-child {
border-radius: 3px;
}
.assistant-panel-chart-value {
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
min-width: 24px;
}
.assistant-panel-chart-legend {
display: flex;
gap: 12px;
flex-wrap: wrap;
font-size: 11px;
padding-top: 4px;
border-top: 1px solid var(--vscode-panel-border);
}
.assistant-panel-chart-legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.assistant-panel-chart-legend-swatch {
width: 10px;
height: 10px;
border-radius: 2px;
flex-shrink: 0;
}
.assistant-panel-form {
display: flex;
flex-direction: column;
gap: 8px;
@@ -176,12 +245,12 @@
padding: 8px;
}
.assistant-sidebar-form-title {
.assistant-panel-form-title {
margin: 0;
font-weight: 600;
}
.assistant-sidebar-card {
.assistant-panel-card {
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 8px;
@@ -190,57 +259,57 @@
gap: 6px;
}
.assistant-sidebar-card h4,
.assistant-sidebar-card p {
.assistant-panel-card h4,
.assistant-panel-card p {
margin: 0;
}
.assistant-sidebar-card-subtitle {
.assistant-panel-card-subtitle {
font-size: 12px;
opacity: 0.8;
}
.assistant-sidebar-card-actions {
.assistant-panel-card-actions {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.assistant-sidebar-image {
.assistant-panel-image {
margin: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.assistant-sidebar-image img {
.assistant-panel-image img {
max-width: 100%;
border-radius: 6px;
border: 1px solid var(--vscode-panel-border);
}
.assistant-sidebar-image figcaption {
.assistant-panel-image figcaption {
font-size: 12px;
opacity: 0.85;
}
.assistant-sidebar-tabs {
.assistant-panel-tabs {
display: flex;
flex-direction: column;
gap: 8px;
}
.assistant-sidebar-tab-strip {
.assistant-panel-tab-strip {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.assistant-sidebar-tab-button.active {
.assistant-panel-tab-button.active {
border-color: var(--vscode-focusBorder);
}
.assistant-sidebar-tab-panel {
.assistant-panel-tab-panel {
border: 1px solid var(--vscode-panel-border);
border-radius: 6px;
padding: 8px;