style(watchlist): the toolbar is two atomic groups — no more orphan chip
Add-controls left, filters right, justify-between: when the pane narrows, the whole filter group drops below as a block instead of shedding its last chip. Shorter chip labels (Active / Needed) and a slimmer search keep both groups on one line at the tab's normal width.
This commit is contained in:
@@ -236,8 +236,9 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* toolbar */}
|
{/* toolbar */}
|
||||||
<div className="flex items-center gap-x-2 gap-y-1.5 flex-wrap">
|
<div className="flex items-start justify-between gap-x-3 gap-y-1.5 flex-wrap">
|
||||||
<Input className="h-8 w-44 font-mono placeholder:font-sans placeholder:normal-case" placeholder={t('wl.addPh')} value={addCall}
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
|
<Input className="h-8 w-40 font-mono placeholder:font-sans placeholder:normal-case" placeholder={t('wl.addPh')} value={addCall}
|
||||||
onChange={(e) => setAddCall(e.target.value.toUpperCase())}
|
onChange={(e) => setAddCall(e.target.value.toUpperCase())}
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter') void add(); }} />
|
onKeyDown={(e) => { if (e.key === 'Enter') void add(); }} />
|
||||||
<label className="flex items-center gap-1.5 text-xs cursor-pointer" title={t('wl.contestHint')}>
|
<label className="flex items-center gap-1.5 text-xs cursor-pointer" title={t('wl.contestHint')}>
|
||||||
@@ -252,10 +253,11 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
|
|||||||
value={pattern}
|
value={pattern}
|
||||||
onChange={(e) => setPattern(e.target.value.toUpperCase())}
|
onChange={(e) => setPattern(e.target.value.toUpperCase())}
|
||||||
onBlur={() => { void SetWatchlistContestPattern(pattern.trim()); }} />
|
onBlur={() => { void SetWatchlistContestPattern(pattern.trim()); }} />
|
||||||
<div className="flex-1" />
|
</div>
|
||||||
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="size-3.5 absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
<Search className="size-3.5 absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
||||||
<Input className="h-8 w-32 pl-7 text-sm" placeholder={t('wl.searchPh')} value={search}
|
<Input className="h-8 w-28 pl-7 text-sm" placeholder={t('wl.searchPh')} value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)} />
|
onChange={(e) => setSearch(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
{/* families kept together, told apart — per review of the DXHunter port,
|
{/* families kept together, told apart — per review of the DXHunter port,
|
||||||
@@ -284,6 +286,7 @@ export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: P
|
|||||||
className={cn('px-2 py-1 rounded-md border text-xs', neededOnly ? 'bg-accent border-border font-medium' : 'border-border text-muted-foreground hover:bg-accent/50')}>
|
className={cn('px-2 py-1 rounded-md border text-xs', neededOnly ? 'bg-accent border-border font-medium' : 'border-border text-muted-foreground hover:bg-accent/50')}>
|
||||||
{t('wl.neededOnly')}
|
{t('wl.neededOnly')}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{error && <div className="text-xs text-danger px-1">{error}</div>}
|
{error && <div className="text-xs text-danger px-1">{error}</div>}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const en: Dict = {
|
|||||||
'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', 'wl.cTotal': 'Watchlist:', 'wl.cActive': 'Active:', 'wl.cNeeded': 'Needed:', 'wl.allModes': 'All modes', 'wl.modeFilter': 'Only show spots in this mode', 'wl.patternPh': 'Auto contest (e.g. WWA)', 'wl.patternHint': 'Auto-add as contest: any spotted callsign CONTAINING this text joins the watchlist as a contest entry by itself. Empty = off; collected entries stay.',
|
'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', 'wl.cTotal': 'Watchlist:', 'wl.cActive': 'Active:', 'wl.cNeeded': 'Needed:', 'wl.allModes': 'All modes', 'wl.modeFilter': 'Only show spots in this mode', 'wl.patternPh': 'Auto contest (e.g. WWA)', 'wl.patternHint': 'Auto-add as contest: any spotted callsign CONTAINING this text joins the watchlist as a contest entry by itself. Empty = off; collected entries stay.',
|
||||||
'wl.contestHint': 'Contest station: worked/needed is judged against the current UTC day — at 00:00 UTC every slot can be worked again.',
|
'wl.contestHint': 'Contest station: worked/needed is judged against the current UTC day — at 00:00 UTC every slot can be worked again.',
|
||||||
'wl.searchPh': 'Search…', 'wl.famAll': 'All', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
'wl.searchPh': 'Search…', 'wl.famAll': 'All', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||||
'wl.activeOnly': 'Active only', 'wl.neededOnly': 'Needed only',
|
'wl.activeOnly': 'Active', 'wl.neededOnly': 'Needed',
|
||||||
'wl.empty': 'Add the callsigns or prefixes you are hunting — a prefix catches the portable forms too (RI0SP matches RI0SP/MM). Spots from the cluster appear under each entry with what they are worth.',
|
'wl.empty': 'Add the callsigns or prefixes you are hunting — a prefix catches the portable forms too (RI0SP matches RI0SP/MM). Spots from the cluster appear under each entry with what they are worth.',
|
||||||
'wl.noneMatch': 'Nothing matches the current filters.',
|
'wl.noneMatch': 'Nothing matches the current filters.',
|
||||||
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpedition',
|
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpedition',
|
||||||
@@ -566,7 +566,7 @@ const fr: Dict = {
|
|||||||
'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', 'wl.cTotal': 'Watchlist :', 'wl.cActive': 'Actives :', 'wl.cNeeded': 'Manquantes :', 'wl.allModes': 'Tous les modes', 'wl.modeFilter': 'Ne montrer que les spots de ce mode', 'wl.patternPh': 'Auto contest (ex. WWA)', 'wl.patternHint': "Ajout auto comme contest : tout indicatif spotté CONTENANT ce texte rejoint la watchlist en entrée contest tout seul. Vide = désactivé ; les entrées déjà collectées restent.",
|
'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', 'wl.cTotal': 'Watchlist :', 'wl.cActive': 'Actives :', 'wl.cNeeded': 'Manquantes :', 'wl.allModes': 'Tous les modes', 'wl.modeFilter': 'Ne montrer que les spots de ce mode', 'wl.patternPh': 'Auto contest (ex. WWA)', 'wl.patternHint': "Ajout auto comme contest : tout indicatif spotté CONTENANT ce texte rejoint la watchlist en entrée contest tout seul. Vide = désactivé ; les entrées déjà collectées restent.",
|
||||||
'wl.contestHint': "Station contest : contacté/manquant est jugé sur la journée UTC courante — à 00:00 UTC chaque créneau redevient à faire.",
|
'wl.contestHint': "Station contest : contacté/manquant est jugé sur la journée UTC courante — à 00:00 UTC chaque créneau redevient à faire.",
|
||||||
'wl.searchPh': 'Chercher…', 'wl.famAll': 'Tous', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
'wl.searchPh': 'Chercher…', 'wl.famAll': 'Tous', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||||
'wl.activeOnly': 'Actifs seulement', 'wl.neededOnly': 'Manquants seulement',
|
'wl.activeOnly': 'Actifs', 'wl.neededOnly': 'Manquants',
|
||||||
'wl.empty': "Ajoutez les indicatifs ou préfixes que vous chassez — un préfixe attrape aussi les formes portables (RI0SP matche RI0SP/MM). Les spots du cluster apparaissent sous chaque entrée avec ce qu'ils valent.",
|
'wl.empty': "Ajoutez les indicatifs ou préfixes que vous chassez — un préfixe attrape aussi les formes portables (RI0SP matche RI0SP/MM). Les spots du cluster apparaissent sous chaque entrée avec ce qu'ils valent.",
|
||||||
'wl.noneMatch': 'Rien ne correspond aux filtres actuels.',
|
'wl.noneMatch': 'Rien ne correspond aux filtres actuels.',
|
||||||
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpédition',
|
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpédition',
|
||||||
|
|||||||
Reference in New Issue
Block a user