mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
refactor(atlas): draw the country and place search rows with one row component
The country rows and the geocoded place rows repeated the same button, hover tint and trailing chevron. A ResultRow inside AtlasCountrySearch now draws that frame and each list passes its own leading content.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '../../../tests/helpers/render';
|
||||
import AtlasCountrySearch from './AtlasCountrySearch';
|
||||
import type { AtlasPlaceHit } from './atlasModel';
|
||||
|
||||
const PLACE: AtlasPlaceHit = { name: 'Lisbon', address: 'Portugal', lat: 38.72, lng: -9.14 };
|
||||
|
||||
function renderSearch(overrides: Partial<Parameters<typeof AtlasCountrySearch>[0]> = {}) {
|
||||
const props = {
|
||||
dark: false,
|
||||
t: (key: string) => key,
|
||||
search: 'po',
|
||||
setSearch: vi.fn(),
|
||||
results: [{ code: 'PT', label: 'Portugal' }],
|
||||
setResults: vi.fn(),
|
||||
open: true,
|
||||
setOpen: vi.fn(),
|
||||
options: [],
|
||||
onSelect: vi.fn(),
|
||||
placeResults: [PLACE],
|
||||
placesLoading: false,
|
||||
onQueryChange: vi.fn(),
|
||||
onSelectPlace: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
return { props, ...render(<AtlasCountrySearch {...props} />) };
|
||||
}
|
||||
|
||||
describe('AtlasCountrySearch result rows', () => {
|
||||
it('draws a country and a place row the same way, each ending in a chevron', () => {
|
||||
renderSearch();
|
||||
const country = screen
|
||||
.getByText('Portugal', { selector: 'span.text-content' })
|
||||
.closest('button') as HTMLButtonElement;
|
||||
const place = screen.getByText('Lisbon').closest('button') as HTMLButtonElement;
|
||||
for (const row of [country, place]) {
|
||||
expect(row.style.padding).toBe('10px 12px');
|
||||
expect(row.style.borderBottom).toBe('1px solid rgba(0, 0, 0, 0.06)');
|
||||
expect(row.lastElementChild?.tagName.toLowerCase()).toBe('svg');
|
||||
}
|
||||
expect(country.querySelector('img')).toHaveAttribute('src', 'https://flagcdn.com/w40/pt.png');
|
||||
expect(place).toHaveTextContent('Portugal');
|
||||
});
|
||||
|
||||
it('selects the country or the place that was clicked', () => {
|
||||
const { props } = renderSearch();
|
||||
fireEvent.click(screen.getByAltText('PT').closest('button') as HTMLButtonElement);
|
||||
expect(props.onSelect).toHaveBeenCalledWith('PT');
|
||||
fireEvent.click(screen.getByText('Lisbon').closest('button') as HTMLButtonElement);
|
||||
expect(props.onSelectPlace).toHaveBeenCalledWith(PLACE);
|
||||
});
|
||||
|
||||
it('tints a row on hover in the colour of the scheme and clears it on leave', () => {
|
||||
renderSearch({ dark: true });
|
||||
const row = screen.getByText('Lisbon').closest('button') as HTMLButtonElement;
|
||||
expect(row.style.borderBottom).toBe('1px solid rgba(255, 255, 255, 0.06)');
|
||||
fireEvent.mouseEnter(row);
|
||||
expect(row.style.background).toBe('rgba(255, 255, 255, 0.06)');
|
||||
fireEvent.mouseLeave(row);
|
||||
expect(row.style.background).toBe('transparent');
|
||||
});
|
||||
});
|
||||
@@ -142,33 +142,14 @@ export default function AtlasCountrySearch({
|
||||
}}
|
||||
>
|
||||
{results.map((r) => (
|
||||
<button type="button"
|
||||
key={r.code}
|
||||
onClick={() => onSelect(r.code)}
|
||||
style={{
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
cursor: 'pointer',
|
||||
padding: '10px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
fontFamily: 'inherit',
|
||||
textAlign: 'start',
|
||||
borderBottom: '1px solid ' + (dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)'),
|
||||
}}
|
||||
onMouseEnter={(e) => { (e.currentTarget as HTMLButtonElement).style.background = dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)' }}
|
||||
onMouseLeave={(e) => { (e.currentTarget as HTMLButtonElement).style.background = 'transparent' }}
|
||||
>
|
||||
<ResultRow key={r.code} dark={dark} onClick={() => onSelect(r.code)}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
|
||||
<img src={`https://flagcdn.com/w40/${r.code.toLowerCase()}.png`} alt={r.code} style={{ width: 28, height: 20, borderRadius: 4, objectFit: 'cover' }} />
|
||||
<span className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 650, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{r.label}
|
||||
</span>
|
||||
</span>
|
||||
<ChevronRight size={16} className="text-content-faint" style={{ flexShrink: 0 }} />
|
||||
</button>
|
||||
</ResultRow>
|
||||
))}
|
||||
|
||||
{(placeResults.length > 0 || placesLoading) && (
|
||||
@@ -187,25 +168,7 @@ export default function AtlasCountrySearch({
|
||||
</div>
|
||||
)}
|
||||
{placeResults.map((p) => (
|
||||
<button type="button"
|
||||
key={`${p.lat},${p.lng},${p.name}`}
|
||||
onClick={() => onSelectPlace(p)}
|
||||
style={{
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
cursor: 'pointer',
|
||||
padding: '10px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
fontFamily: 'inherit',
|
||||
textAlign: 'start',
|
||||
borderBottom: '1px solid ' + (dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)'),
|
||||
}}
|
||||
onMouseEnter={(e) => { (e.currentTarget as HTMLButtonElement).style.background = dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)' }}
|
||||
onMouseLeave={(e) => { (e.currentTarget as HTMLButtonElement).style.background = 'transparent' }}
|
||||
>
|
||||
<ResultRow key={`${p.lat},${p.lng},${p.name}`} dark={dark} onClick={() => onSelectPlace(p)}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
|
||||
<span
|
||||
style={{
|
||||
@@ -227,8 +190,7 @@ export default function AtlasCountrySearch({
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<ChevronRight size={16} className="text-content-faint" style={{ flexShrink: 0 }} />
|
||||
</button>
|
||||
</ResultRow>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -236,3 +198,34 @@ export default function AtlasCountrySearch({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** One row of the dropdown, a country or a geocoded place, with the chevron at its end. */
|
||||
function ResultRow({ dark, onClick, children }: {
|
||||
dark: boolean
|
||||
onClick: () => void
|
||||
children: React.ReactNode
|
||||
}): React.ReactElement {
|
||||
return (
|
||||
<button type="button"
|
||||
onClick={onClick}
|
||||
style={{
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
cursor: 'pointer',
|
||||
padding: '10px 12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
fontFamily: 'inherit',
|
||||
textAlign: 'start',
|
||||
borderBottom: '1px solid ' + (dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)'),
|
||||
}}
|
||||
onMouseEnter={(e) => { (e.currentTarget as HTMLButtonElement).style.background = dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)' }}
|
||||
onMouseLeave={(e) => { (e.currentTarget as HTMLButtonElement).style.background = 'transparent' }}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight size={16} className="text-content-faint" style={{ flexShrink: 0 }} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user