🐛 fix(vscode): 下拉按最长项加宽,避免 profile 名被截断

This commit is contained in:
2026-08-27 13:15:46 +08:00
parent b242a64267
commit 70575e7ac6
3 changed files with 46 additions and 17 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"publisher": "clurdra",
"name": "superpowers-vscode-clurdra",
"displayName": "Superpowers-clurdra",
"version": "0.2.97",
"version": "0.2.98",
"packageManager": "pnpm@10.27.0",
"description": "Superpowers specs and plans Kanban explorer",
"author": "clurdra",
@@ -145,6 +145,7 @@ export function ManagedSessionsPanel({
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<span className="w-12 shrink-0 text-xs text-[var(--vscode-descriptionForeground)]">Profile</span>
<div className="min-w-0 flex-1">
<SelectMenu
value={selectedProfile}
onChange={setSelectedProfile}
@@ -152,9 +153,9 @@ export function ManagedSessionsPanel({
? [{ value: '', label: '(无可用 profile' }]
: profiles.map(p => ({ value: p.path, label: p.name }))}
variant="input"
className="min-w-0 flex-1"
/>
</div>
</div>
<div className="flex items-center gap-2">
<span className="w-12 shrink-0 text-xs text-[var(--vscode-descriptionForeground)]"></span>
<div className="relative min-w-0 flex-1" ref={importWrapRef}>
@@ -22,6 +22,25 @@ export interface SelectMenuProps {
}
const MAX_MENU_HEIGHT = 240
/** 勾选图标 + gap + 水平 padding + 竖滚动条槽。少算会把最后几个字挤成省略号。 */
const MENU_CHROME_PX = 14 + 6 + 16 + 12
function measureOptionsWidth(options: SelectMenuOption[], font: string): number {
const probe = document.createElement('span')
probe.style.cssText = `position:absolute;left:-9999px;top:0;white-space:nowrap;font:${font}`
document.body.appendChild(probe)
let max = 0
for (const o of options) {
probe.textContent = o.label
max = Math.max(max, probe.offsetWidth)
if (o.hint) {
probe.textContent = o.hint
max = Math.max(max, probe.offsetWidth)
}
}
probe.remove()
return max
}
/**
* 自定义下拉组件,替代原生 select。
@@ -57,12 +76,21 @@ export function SelectMenu({
const spaceBelow = window.innerHeight - rect.bottom
const spaceAbove = rect.top
const openUp = spaceBelow < menuHeight && spaceAbove > spaceBelow
// 跟触发器同宽会把长 profile 名截成省略号(ghost / 未撑满的 button 尤其明显)。
// 按最长项测量,下限触发器宽、上限视口,溢出时左移,不超出 webview。
const gutter = 8
const contentW = measureOptionsWidth(options, getComputedStyle(el).font) + MENU_CHROME_PX
const maxWidth = window.innerWidth - gutter * 2
const width = Math.min(maxWidth, Math.max(rect.width, contentW))
let left = rect.left
if (left + width > window.innerWidth - gutter)
left = Math.max(gutter, window.innerWidth - gutter - width)
setPos({
top: openUp ? Math.max(8, rect.top - menuHeight - 4) : rect.bottom + 4,
left: rect.left,
width: Math.max(rect.width, 120),
left,
width,
})
}, [options.length])
}, [options])
const openMenu = useCallback((): void => {
computePos()
@@ -181,7 +209,7 @@ export function SelectMenu({
const triggerClasses
= variant === 'input'
? 'rounded border border-[var(--vscode-input-border,var(--vscode-panel-border))] bg-[var(--vscode-input-background)] px-2 py-1 text-xs text-[var(--vscode-input-foreground)] focus:ring-1 focus:ring-inset focus:ring-[var(--vscode-focusBorder)]'
? 'w-full rounded border border-[var(--vscode-input-border,var(--vscode-panel-border))] bg-[var(--vscode-input-background)] px-2 py-1 text-xs text-[var(--vscode-input-foreground)] focus:ring-1 focus:ring-inset focus:ring-[var(--vscode-focusBorder)]'
: 'rounded bg-transparent px-1 py-0 text-[10px] text-[var(--vscode-descriptionForeground)] hover:bg-[var(--vscode-toolbar-hoverBackground)]'
return (
@@ -202,7 +230,7 @@ export function SelectMenu({
className,
)}
>
<span className={cn('min-w-0 truncate', selected ? '' : 'opacity-50')}>
<span className={cn('min-w-0 flex-1 truncate', selected ? '' : 'opacity-50')}>
{selected?.label ?? placeholder}
</span>
<ChevronDown className={cn('size-3.5 shrink-0', variant === 'ghost' && 'size-3')} />
@@ -244,8 +272,8 @@ export function SelectMenu({
<Check className="size-3.5" />
</span>
<span className="flex min-w-0 flex-col">
<span className="truncate">{opt.label}</span>
{opt.hint && <span className="truncate text-[10px] opacity-70">{opt.hint}</span>}
<span className="truncate" title={opt.label}>{opt.label}</span>
{opt.hint && <span className="truncate text-[10px] opacity-70" title={opt.hint}>{opt.hint}</span>}
</span>
</button>
))}