mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 22:46:32 +00:00
66 lines
2.9 KiB
TypeScript
66 lines
2.9 KiB
TypeScript
import { useId, type ReactNode } from 'react'
|
|
import type { TourListItem } from '@trek/shared'
|
|
import { fs } from '../shared/DialogShell'
|
|
import { useTranslation } from '../../i18n'
|
|
import { TourFactPills, TourGradeTile } from './tourParts'
|
|
import { tourWebsitePresentation } from './tourPresentation'
|
|
|
|
interface TourListRowProps {
|
|
tour: TourListItem
|
|
selected?: boolean
|
|
disabled?: boolean
|
|
onSelect: (tour: TourListItem, opener: HTMLElement) => void
|
|
action?: ReactNode
|
|
children?: ReactNode
|
|
}
|
|
|
|
/** A tour in a list, shaped like a place row: tile, name, its facts as pills and the day action on the right. */
|
|
export default function TourListRow({ tour, selected = false, disabled = false, onSelect, action, children }: TourListRowProps) {
|
|
const { t } = useTranslation()
|
|
const nameId = useId()
|
|
const tone = selected ? 'bg-surface-selected' : disabled ? '' : 'hover:bg-surface-hover'
|
|
const website = tourWebsitePresentation(tour.website)
|
|
return (
|
|
<li
|
|
role="option"
|
|
tabIndex={disabled ? -1 : 0}
|
|
aria-selected={selected}
|
|
aria-disabled={disabled || undefined}
|
|
aria-labelledby={nameId}
|
|
className={`group mb-px rounded-[12px] px-2.5 py-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[color:var(--text-primary)] ${tone} ${disabled ? 'cursor-default opacity-60' : 'cursor-pointer'}`}
|
|
onClick={event => { if (!disabled) onSelect(tour, event.currentTarget) }}
|
|
onKeyDown={event => {
|
|
if (disabled || event.target !== event.currentTarget) return
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
event.preventDefault()
|
|
onSelect(tour, event.currentTarget)
|
|
}
|
|
}}
|
|
>
|
|
<div className="flex items-start gap-2.5">
|
|
<TourGradeTile tour={tour} />
|
|
<div className="min-w-0 flex-1 pt-px">
|
|
<span id={nameId} className="block min-w-0 truncate font-semibold leading-tight text-content" style={fs(13, 'body')}>{tour.name}</span>
|
|
<TourFactPills tour={tour} className="mt-1.5" />
|
|
{tour.description && (
|
|
<div data-testid="tour-description-preview" className="mt-1 overflow-hidden text-ellipsis text-content-faint" style={{ ...fs(11), lineHeight: 1.25, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' }}>
|
|
{tour.description}
|
|
</div>
|
|
)}
|
|
{website && (
|
|
<div className="mt-1 truncate text-content-faint" style={fs(10.5)}>
|
|
{website.provider ? `${website.provider} · ${website.domain}` : `${t('places.formWebsite')} · ${website.domain}`}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{action && (
|
|
<div data-testid="tour-action-row" role="presentation" className="flex flex-none items-center gap-1.5 pt-1" onClick={event => event.stopPropagation()}>
|
|
{action}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{children}
|
|
</li>
|
|
)
|
|
}
|