Files
TREK/client/src/components/shared/CountryFlag.tsx
T
Maurice 15aefa449e feat(journey): the marker card, and what testing it turned up
Everything here came out of putting the first commit in front of somebody and
watching them use it.

**The marker card is one card now, and it carries the entry's photos.** Leaflet
showed a one-line label and the GL map a two-line card, so the same marker said
different things depending on a renderer setting nobody connects to journeys.
Both now build from one module: a strip of up to three thumbnails bleeding to
the card's edges, the name, and the place and date as chips. On the GL map the
thumbnails open the entry's photos; the rest of the card still never takes the
pointer, so the map drags from anywhere on it.

**A pin that would not sit still.** Dragging the map left the marker trailing
behind and snapping back at the end. The app's press-feedback rule puts a 180ms
transition on `[role="button"]`, MapLibre and Mapbox give their marker element
exactly that role, and both rewrite its `transform` on every frame of a
movement. Markers are excluded now — every map in TREK, not just this one.

**The phone card, after four rounds of looking at it.** The day is the card's
own edge rather than a bar across its foot, and the ring is inset because the
carousel clips. A card without a photo is opaque: it was a wash of the day's
colour over the map. The country is a real flag rather than two letters —
Windows has no flag glyphs, so the emoji drew "DE" where a flag was meant, and
the image falls back to the emoji when it cannot be fetched. Mood, weather and
the date share the top edge; the flag sits opposite them.

**The day bar answers to a finger.** Press the date and slide sideways to run
through the days. The bar is still a row of buttons underneath, because that is
the only version of it a keyboard can reach, and each one is 20px tall now
rather than 3.

Also here:

- Videos upload to an entry (issue #2341). The gallery has taken them since
  #823 and an entry could not: everything went through the images-only filter
  and came back 400, reported as "1 of 1 photos failed". Same two-part upload,
  same absence of transcoding, and the editor now previews a clip in a `<video>`
  instead of a broken-image glyph.
- The search box takes the gap the toolbar already had instead of clipping its
  own placeholder.
- Entry cards line up with the day header again: the reorder arrows moved out of
  a column of their own into the feed's padding and appear on hover, and the
  day-coloured ring on the active card is a layer over it — an outline tight
  enough to sit on the card's edge was painting behind it.
- Map attribution is collapsed to its ⓘ. It cannot go away — the OSM licence
  asks for it — but it does not have to lie under the carousel.
- The suggestion dismiss and the day-header plus use TREK's own tooltip rather
  than the browser's.
2026-09-13 19:52:14 +02:00

48 lines
1.8 KiB
TypeScript

import { useState } from 'react'
import { countryCodeToFlag } from '../../pages/atlas/atlasModel'
/**
* A country's flag, drawn on every platform.
*
* The emoji alone is not enough. A flag is two regional-indicator code points,
* and Windows has never shipped glyphs for the pairs — Chrome and Edge there
* fall back to drawing the two letters, so `DE` appears where a flag was meant
* and reads as something broken. Atlas has always had that, and the journey
* cards inherited it.
*
* So the emoji is turned into its Twemoji image, the same way the collaboration
* chat draws emoji (`CollabChatTwemojiImg`). When that image cannot be fetched —
* offline, or an install that blocks the CDN — the emoji itself is rendered
* instead, which is the old behaviour: a flag on Apple and Android, two letters
* on Windows. Nothing is worse than it was, and on most screens it is a flag.
*/
export default function CountryFlag({ code, size = 14, title }: { code: string | null | undefined; size?: number; title?: string }) {
const [failed, setFailed] = useState(false)
if (!code || !/^[A-Za-z]{2}$/.test(code)) return null
const emoji = countryCodeToFlag(code)
if (!emoji) return null
if (failed) {
return (
<span aria-hidden style={{ fontSize: size, lineHeight: 1 }} title={title}>
{emoji}
</span>
)
}
const cp = [...code.toUpperCase()]
.map(c => (0x1f1e6 + c.charCodeAt(0) - 65).toString(16))
.join('-')
return (
<img
src={`https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72/${cp}.png`}
alt={code.toUpperCase()}
title={title}
draggable={false}
loading="lazy"
onError={() => setFailed(true)}
style={{ width: size, height: size, display: 'block', borderRadius: 2 }}
/>
)
}