fix(NodeSwitcher): use custom dropdown instead of native select for consistent styling
Build and Deploy / build-and-push (push) Failing after 1m1s
Build and Deploy / build-and-push (push) Failing after 1m1s
This commit is contained in:
@@ -14,6 +14,7 @@ export function NodeSwitcher() {
|
|||||||
const [nodes, setNodes] = useState<InterconnectNode[]>([])
|
const [nodes, setNodes] = useState<InterconnectNode[]>([])
|
||||||
const [selected, setSelected] = useState(activeInterconnectNodeId || 'local')
|
const [selected, setSelected] = useState(activeInterconnectNodeId || 'local')
|
||||||
const [isMaster, setIsMaster] = useState(false)
|
const [isMaster, setIsMaster] = useState(false)
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
const fetchNodes = useCallback(async () => {
|
const fetchNodes = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -73,38 +74,64 @@ export function NodeSwitcher() {
|
|||||||
|
|
||||||
if (!isMaster) return null
|
if (!isMaster) return null
|
||||||
|
|
||||||
|
const handleSelect = (val: string) => {
|
||||||
|
setSelected(val)
|
||||||
|
setOpen(false)
|
||||||
|
if (val === 'local') {
|
||||||
|
setActiveInterconnectNodeId('')
|
||||||
|
} else {
|
||||||
|
const name = nodes.find((n) => n.id === val)?.name || ''
|
||||||
|
setActiveInterconnectNodeId(val, name)
|
||||||
|
}
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const allOptions = [
|
||||||
|
{ value: 'local', label: '本机节点' },
|
||||||
|
...nodes.map((n) => ({ value: n.id, label: n.name })),
|
||||||
|
]
|
||||||
|
|
||||||
|
const currentLabel = allOptions.find((o) => o.value === selected)?.label || '本机节点'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="relative">
|
||||||
className={cn(
|
<button
|
||||||
'inline-flex h-8 max-w-[150px] shrink-0 items-center gap-1 rounded-md border px-2 text-[11px] leading-none',
|
type="button"
|
||||||
selected !== 'local'
|
onClick={() => setOpen(!open)}
|
||||||
? 'border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400'
|
className={cn(
|
||||||
: 'border-[var(--border)] bg-[var(--bg-primary)] text-[var(--text-secondary)]',
|
'inline-flex h-8 max-w-[150px] shrink-0 items-center gap-1.5 rounded-md border px-2 text-[11px] leading-none transition-colors',
|
||||||
)}
|
selected !== 'local'
|
||||||
>
|
? 'border-amber-500/30 bg-amber-500/10 text-amber-600 hover:bg-amber-500/15 dark:text-amber-400'
|
||||||
<MonitorDot size={12} className="shrink-0" />
|
: 'border-[var(--border)] bg-[var(--bg-primary)] text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]',
|
||||||
<select
|
)}
|
||||||
className="max-w-[120px] truncate border-0 bg-transparent text-[11px] outline-none"
|
|
||||||
value={selected}
|
|
||||||
onChange={(e) => {
|
|
||||||
const val = e.target.value
|
|
||||||
setSelected(val)
|
|
||||||
if (val === 'local') {
|
|
||||||
setActiveInterconnectNodeId('')
|
|
||||||
} else {
|
|
||||||
const name = nodes.find((n) => n.id === val)?.name || ''
|
|
||||||
setActiveInterconnectNodeId(val, name)
|
|
||||||
}
|
|
||||||
window.location.reload()
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<option value="local">本机节点</option>
|
<MonitorDot size={12} className="shrink-0" />
|
||||||
{nodes.map((n) => (
|
<span className="max-w-[100px] truncate">{currentLabel}</span>
|
||||||
<option key={n.id} value={n.id}>
|
</button>
|
||||||
{n.name}
|
|
||||||
</option>
|
{open && (
|
||||||
))}
|
<div className="absolute left-0 top-full z-[80] mt-1 min-w-[140px] rounded-md border border-[var(--border)] bg-[var(--bg-secondary)] py-1 shadow-lg">
|
||||||
</select>
|
{allOptions.map((opt) => (
|
||||||
|
<button
|
||||||
|
key={opt.value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleSelect(opt.value)}
|
||||||
|
className={cn(
|
||||||
|
'w-full px-3 py-1.5 text-left text-xs transition-colors',
|
||||||
|
opt.value === selected
|
||||||
|
? 'bg-[var(--bg-selected)] text-[var(--text-primary)]'
|
||||||
|
: 'text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-primary)]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<div className="fixed inset-0 z-[70]" onClick={() => setOpen(false)} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user