mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
126 lines
4.8 KiB
TypeScript
126 lines
4.8 KiB
TypeScript
import { Check, ExternalLink, MapPinPlus, MoveHorizontal, Route, X } from 'lucide-react';
|
|
|
|
import type { Ferrata } from '@trek/shared';
|
|
|
|
import { useTranslation } from '../../i18n';
|
|
import { useSettingsStore } from '../../store/settingsStore';
|
|
import { formatDistance } from '../../utils/units';
|
|
import { MAP_CONTROL_SHADOW } from '../Map/mapControlShadow';
|
|
import { SoftPill } from '../Planner/planParts';
|
|
import { fs } from '../shared/DialogShell';
|
|
import { type FerrataGroup, ferrataLabel } from './ferrataGroups';
|
|
import { ferrataOsmUrl } from './ferrataModel';
|
|
|
|
/** The map controls' frosted glass, so the card reads as one of them. */
|
|
const MAP_GLASS = {
|
|
background: 'var(--sidebar-bg)',
|
|
backdropFilter: 'blur(20px) saturate(180%)',
|
|
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
|
|
boxShadow: MAP_CONTROL_SHADOW,
|
|
};
|
|
|
|
/**
|
|
* One via ferrata, picked on the map: its name, the route it belongs to, its
|
|
* grade range and total length as badges, and the way back to the source.
|
|
* `segment` is the OSM way that was clicked, which the source link opens.
|
|
* OpenStreetMap's data is ODbL, which asks for the attribution next to
|
|
* anything shown from it, so the card carries it rather than relying on the
|
|
* map's own corner.
|
|
*/
|
|
export default function FerrataDetailCard({
|
|
group,
|
|
segment,
|
|
onClose,
|
|
onAdopt,
|
|
onTour = false,
|
|
width,
|
|
}: {
|
|
group: FerrataGroup;
|
|
segment: Ferrata;
|
|
onClose?: () => void;
|
|
/** Puts the ferrata onto the tour being planned; absent where the tour cannot be edited. */
|
|
onAdopt?: (group: FerrataGroup) => void;
|
|
/** The tour on the map already climbs this ferrata: the card says so instead of offering it again. */
|
|
onTour?: boolean;
|
|
/** Set when the card stands on another control and takes its width. */
|
|
width?: number;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const unit = useSettingsStore((s) => s.settings.distance_unit);
|
|
const label = ferrataLabel(group, t);
|
|
const name = label.name;
|
|
// The route line only adds something when it is not already the title.
|
|
const route = group.routeName && group.routeName !== name ? group.routeName : null;
|
|
// Without any grade the badge says so, rather than leaving a gap.
|
|
const grade = label.grade ?? t('ferratas.unknownGrade');
|
|
|
|
return (
|
|
<article
|
|
className={`rounded-2xl p-3 text-content ${width ? '' : 'w-max min-w-[min(18rem,calc(100vw_-_2rem))] max-w-[22.5rem]'}`}
|
|
style={{ ...MAP_GLASS, width }}
|
|
data-testid="ferrata-detail-card"
|
|
>
|
|
<div className="flex items-start gap-2">
|
|
<Route size={16} className="mt-0.5 flex-none text-content-muted" aria-hidden="true" />
|
|
<div className="min-w-0 flex-1">
|
|
<h3 className="line-clamp-2 break-words font-semibold" style={fs(14, 'body')} title={name}>
|
|
{name}
|
|
</h3>
|
|
{route && <p className="truncate text-caption text-content-muted">{route}</p>}
|
|
</div>
|
|
{onClose && (
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
aria-label={t('common.close')}
|
|
className="flex-none rounded-lg p-1 text-content-muted hover:bg-surface-hover hover:text-content"
|
|
>
|
|
<X size={14} aria-hidden="true" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
<div className="mt-2 flex flex-wrap items-center gap-2">
|
|
<SoftPill>{grade}</SoftPill>
|
|
{group.lengthM > 0 && (
|
|
<SoftPill icon={<MoveHorizontal size={11} aria-hidden="true" />}>
|
|
{formatDistance(group.lengthM / 1000, unit ?? 'metric')}
|
|
</SoftPill>
|
|
)}
|
|
</div>
|
|
{onTour ? (
|
|
<div className="mt-3 flex" data-testid="ferrata-on-tour">
|
|
<SoftPill tone="success" icon={<Check size={11} strokeWidth={2.4} className="flex-none" />}>
|
|
{t('ferratas.onTour')}
|
|
</SoftPill>
|
|
</div>
|
|
) : (
|
|
onAdopt && (
|
|
<button
|
|
type="button"
|
|
onClick={() => onAdopt(group)}
|
|
data-testid="ferrata-adopt"
|
|
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-full bg-accent px-3 py-1.5 font-semibold text-accent-text shadow-sm transition-opacity hover:opacity-90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent"
|
|
style={fs(12, 'body')}
|
|
>
|
|
<MapPinPlus size={13} strokeWidth={2.2} aria-hidden="true" />
|
|
{t('ferratas.adopt')}
|
|
</button>
|
|
)
|
|
)}
|
|
<a
|
|
href={ferrataOsmUrl(segment)}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="mt-3 inline-flex items-center gap-1 font-medium text-content hover:underline"
|
|
style={fs(12, 'body')}
|
|
>
|
|
{t('ferratas.viewInOsm')}
|
|
<ExternalLink size={13} aria-hidden="true" />
|
|
</a>
|
|
<p className="mt-2 border-t border-edge-faint pt-2 text-content-faint" style={fs(9)}>
|
|
{t('ferratas.attribution')}
|
|
</p>
|
|
</article>
|
|
);
|
|
}
|