mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +00:00
0dd07befa7
The Plan tab was the last trip tab still in the old design. It now reads like Bookings, Costs, Lists, Files and Collab, without losing a function: - Day plan: each day is a card with a head band (day tile with weather, stay and rental pills, a "+" menu for place, stay, transport, transit and note), rows with "..." menus, white badges and a white route bar. - Places column: add, search and filters in three rows, a show dropdown, category and rating filters, and a selection bar at the foot. - Place inspector, day details and the stay editor on the shared dialog frame; the place form in the editor look with the name in the head band. - Clicking a booking or transport anywhere on the plan opens the booking detail popup first; Edit leads to the editor. The popup is one host shared with the Bookings and Transports tabs. - Files and Collab dialogs, notes, polls, links and chat moved to the same frame; shared DialogShell and dialogParts hold the building blocks. - The map hover card shows the place photo and follows the theme. Every new and reworded string is translated into all locales, and a few older English leftovers (help center, cost split, import hints) with them. Phone screens keep their own look.
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import { useEffect, useId } from 'react'
|
|
import { PencilLine, type LucideIcon } from 'lucide-react'
|
|
import { useTranslation } from '../../i18n'
|
|
import { DialogButton, DialogFooter, DialogHeader, DialogShell, DialogTile, FooterSpacer, NEUTRAL_TINT } from './DialogShell'
|
|
|
|
interface NameDialogProps {
|
|
open: boolean
|
|
title: string
|
|
placeholder: string
|
|
confirmLabel: string
|
|
value: string
|
|
onChange: (value: string) => void
|
|
onConfirm: () => void
|
|
onClose: () => void
|
|
/** The icon on the head band's tile; a pencil unless the caller has a better one. */
|
|
icon?: LucideIcon
|
|
}
|
|
|
|
/**
|
|
* The small dialog that asks for one name: a new list, a template to save, a
|
|
* booking's title. The name is typed into the head band itself; Enter confirms
|
|
* and Escape cancels this dialog alone, never one it was opened from.
|
|
*/
|
|
export default function NameDialog({ open, title, placeholder, confirmLabel, value, onChange, onConfirm, onClose, icon: Icon = PencilLine }: NameDialogProps) {
|
|
const { t } = useTranslation()
|
|
const labelId = useId()
|
|
const ready = value.trim().length > 0
|
|
|
|
// Caught before any dialog underneath hears it, so only this one closes.
|
|
useEffect(() => {
|
|
if (!open) return
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if (e.key !== 'Escape' || e.defaultPrevented) return
|
|
e.preventDefault()
|
|
onClose()
|
|
}
|
|
document.addEventListener('keydown', onKey, true)
|
|
return () => document.removeEventListener('keydown', onKey, true)
|
|
}, [open, onClose])
|
|
|
|
return (
|
|
<DialogShell
|
|
open={open}
|
|
onClose={onClose}
|
|
labelledBy={labelId}
|
|
width="narrow"
|
|
header={(
|
|
<DialogHeader
|
|
tile={<DialogTile><Icon size={20} strokeWidth={1.9} className="text-content-muted" /></DialogTile>}
|
|
tint={NEUTRAL_TINT}
|
|
labelId={labelId}
|
|
onClose={onClose}
|
|
eyebrow={title}
|
|
titleInput={{
|
|
value,
|
|
onChange,
|
|
label: title,
|
|
placeholder,
|
|
autoFocus: true,
|
|
onKeyDown: e => { if (e.key === 'Enter' && ready) onConfirm() },
|
|
}}
|
|
/>
|
|
)}
|
|
footer={(
|
|
<DialogFooter>
|
|
<FooterSpacer />
|
|
<DialogButton onClick={onClose}>{t('common.cancel')}</DialogButton>
|
|
<DialogButton variant="primary" onClick={onConfirm} disabled={!ready}>{confirmLabel}</DialogButton>
|
|
</DialogFooter>
|
|
)}
|
|
/>
|
|
)
|
|
}
|