feat(desktop): add BrowserAddressBar component

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
程序员阿江(Relakkes) 2026-05-29 11:48:53 +08:00
parent 25ea37bb3d
commit bdfedcd2e7
2 changed files with 70 additions and 0 deletions

View File

@ -0,0 +1,37 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import '@testing-library/jest-dom'
import { BrowserAddressBar } from './BrowserAddressBar'
const baseProps = {
url: 'http://localhost:5173/', canGoBack: false, canGoForward: false,
onNavigate: vi.fn(), onBack: vi.fn(), onForward: vi.fn(), onReload: vi.fn(),
}
describe('BrowserAddressBar', () => {
it('shows the current url in the input', () => {
render(<BrowserAddressBar {...baseProps} />)
expect(screen.getByRole('textbox')).toHaveValue('http://localhost:5173/')
})
it('submits the edited url via onNavigate', () => {
const onNavigate = vi.fn()
render(<BrowserAddressBar {...baseProps} onNavigate={onNavigate} />)
const input = screen.getByRole('textbox')
fireEvent.change(input, { target: { value: 'http://localhost:3000/x' } })
fireEvent.submit(input.closest('form')!)
expect(onNavigate).toHaveBeenCalledWith('http://localhost:3000/x')
})
it('disables back when cannot go back', () => {
render(<BrowserAddressBar {...baseProps} canGoBack={false} />)
expect(screen.getByLabelText('后退')).toBeDisabled()
})
it('enables back and fires onBack when canGoBack', () => {
const onBack = vi.fn()
render(<BrowserAddressBar {...baseProps} canGoBack onBack={onBack} />)
fireEvent.click(screen.getByLabelText('后退'))
expect(onBack).toHaveBeenCalled()
})
})

View File

@ -0,0 +1,33 @@
import { useEffect, useState } from 'react'
import { ArrowLeft, ArrowRight, RotateCw } from 'lucide-react'
type Props = {
url: string
canGoBack: boolean
canGoForward: boolean
onNavigate: (url: string) => void
onBack: () => void
onForward: () => void
onReload: () => void
}
export function BrowserAddressBar({ url, canGoBack, canGoForward, onNavigate, onBack, onForward, onReload }: Props) {
const [draft, setDraft] = useState(url)
useEffect(() => { setDraft(url) }, [url])
return (
<div className="flex items-center gap-1 px-2 py-1.5 border-b border-[var(--color-border)]">
<button aria-label="后退" disabled={!canGoBack} onClick={onBack} className="p-1 disabled:opacity-40"><ArrowLeft size={16} /></button>
<button aria-label="前进" disabled={!canGoForward} onClick={onForward} className="p-1 disabled:opacity-40"><ArrowRight size={16} /></button>
<button aria-label="刷新" onClick={onReload} className="p-1"><RotateCw size={16} /></button>
<form className="flex-1" onSubmit={(e) => { e.preventDefault(); onNavigate(draft.trim()) }}>
<input
className="w-full rounded-md bg-[var(--color-surface)] px-2 py-1 text-xs text-[var(--color-text-primary)]"
value={draft}
onChange={(e) => setDraft(e.target.value)}
spellCheck={false}
/>
</form>
</div>
)
}