fix list align

This commit is contained in:
LaZzyMan 2026-03-06 16:21:20 +08:00
parent 4879ea4a36
commit b942c0241f
4 changed files with 73 additions and 22 deletions

View file

@ -351,7 +351,9 @@ export function ExtensionsManagerDialog({
return (
<Box>
<Text bold>{getStepHeaderText()}</Text>
<Text color={theme.text.accent} bold>
{getStepHeaderText()}
</Text>
</Box>
);
}, [getCurrentStep, selectedExtension]);

View file

@ -29,67 +29,90 @@ export const ExtensionDetailStep = ({
const activeColor = isActive ? theme.status.success : theme.text.secondary;
const activeString = isActive ? t('active') : t('disabled');
// Fixed width for labels to ensure alignment
const LABEL_WIDTH = 12;
return (
<Box flexDirection="column" gap={1}>
<Box flexDirection="column">
<Box>
<Text color={theme.text.primary}>{`${t('Name:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Name:')}</Text>
</Box>
<Text>{ext.name}</Text>
</Box>
<Box>
<Text color={theme.text.primary}>{`${t('Version:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Version:')}</Text>
</Box>
<Text>{ext.version}</Text>
</Box>
<Box>
<Text color={theme.text.primary}>{`${t('Status:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Status:')}</Text>
</Box>
<Text color={activeColor}>{activeString}</Text>
</Box>
<Box>
<Text color={theme.text.primary}>{`${t('Path:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Path:')}</Text>
</Box>
<Text>{ext.path}</Text>
</Box>
{ext.installMetadata && (
<Box>
<Text color={theme.text.primary}>{`${t('Source:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Source:')}</Text>
</Box>
<Text>{ext.installMetadata.source}</Text>
</Box>
)}
{ext.mcpServers && Object.keys(ext.mcpServers).length > 0 && (
<Box>
<Text color={theme.text.primary}>{`${t('MCP Servers:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('MCP Servers:')}</Text>
</Box>
<Text>{Object.keys(ext.mcpServers).join(', ')}</Text>
</Box>
)}
{ext.commands && ext.commands.length > 0 && (
<Box>
<Text color={theme.text.primary}>{`${t('Commands:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Commands:')}</Text>
</Box>
<Text>{ext.commands.join(', ')}</Text>
</Box>
)}
{ext.skills && ext.skills.length > 0 && (
<Box>
<Text color={theme.text.primary}>{`${t('Skills:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Skills:')}</Text>
</Box>
<Text>{ext.skills.map((s) => s.name).join(', ')}</Text>
</Box>
)}
{ext.agents && ext.agents.length > 0 && (
<Box>
<Text color={theme.text.primary}>{`${t('Agents:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Agents:')}</Text>
</Box>
<Text>{ext.agents.map((a) => a.name).join(', ')}</Text>
</Box>
)}
{ext.resolvedSettings && ext.resolvedSettings.length > 0 && (
<Box flexDirection="column" marginTop={1}>
<Text color={theme.text.primary}>{`${t('Settings:')} `}</Text>
<Box width={LABEL_WIDTH} flexShrink={0}>
<Text color={theme.text.primary}>{t('Settings:')}</Text>
</Box>
<Box flexDirection="column" paddingLeft={2}>
{ext.resolvedSettings.map((setting) => (
<Text key={setting.name}>

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { useState, useEffect } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { Box, Text } from 'ink';
import { theme } from '../../../semantic-colors.js';
import { useKeypress } from '../../../hooks/useKeypress.js';
@ -25,6 +25,26 @@ export const ExtensionListStep = ({
}: ExtensionListStepProps) => {
const [selectedIndex, setSelectedIndex] = useState(0);
// Calculate max widths for each column for alignment
const { maxNameWidth, maxVersionWidth, maxStatusWidth } = useMemo(() => {
let maxName = 0;
let maxVersion = 0;
let maxStatus = 0;
for (const ext of extensions) {
maxName = Math.max(maxName, ext.name.length);
maxVersion = Math.max(maxVersion, ext.version.length);
const statusLength = ext.isActive
? t('active').length
: t('disabled').length;
maxStatus = Math.max(maxStatus, statusLength);
}
return {
maxNameWidth: maxName,
maxVersionWidth: maxVersion,
maxStatusWidth: maxStatus,
};
}, [extensions]);
// Reset selection when extensions change
useEffect(() => {
if (extensions.length > 0 && selectedIndex >= extensions.length) {
@ -119,15 +139,21 @@ export const ExtensionListStep = ({
{isSelected ? '●' : ' '}
</Text>
</Box>
<Text
color={isSelected ? theme.text.accent : theme.text.primary}
wrap="truncate"
>
{extension.name}
<Box width={maxNameWidth} flexShrink={0}>
<Text
color={isSelected ? theme.text.accent : theme.text.primary}
wrap="truncate"
>
{extension.name}
</Text>
</Box>
<Box width={maxVersionWidth + 3} flexShrink={0}>
<Text color={theme.text.secondary}> v{extension.version}</Text>
<Text color={activeColor}> ({activeString})</Text>
{stateText && <Text color={stateColor}> [{stateText}]</Text>}
</Text>
</Box>
<Box width={maxStatusWidth + 3} flexShrink={0}>
<Text color={activeColor}>({activeString})</Text>
</Box>
{stateText && <Text color={stateColor}>[{stateText}]</Text>}
</Box>
);
};

View file

@ -6,9 +6,9 @@ Use '/extensions install' to install your first extension."
`;
exports[`ExtensionListStep Snapshots > should render list with multiple extensions 1`] = `
"● active-extension v1.0.0 (active) [up to date]
"● active-extension v1.0.0 (active) [up to date]
disabled-extension v1.0.0 (disabled) [not updatable]
update-available v1.0.0 (active) [update available]
update-available v1.0.0 (active) [update available]
3 extensions installed"