mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(planner): keep the PDF tooltip inside the window
The export button is the one toolbar button with a hand-rolled tooltip instead of the shared one. It was anchored with `right: 0` and never wrapped, so its label grew leftwards out of the day plan — the leftmost pane — and off the window. The shared tooltip is portalled and clamped to the viewport, which is what every neighbouring button already uses. The hover state it needed was threaded down from DayPlanSidebar and has no other reader, so it goes too.
This commit is contained in:
@@ -195,7 +195,6 @@ function useDayPlanSidebar(props: DayPlanSidebarProps) {
|
||||
const [lockedIds, setLockedIds] = useState(new Set())
|
||||
const [lockHoverId, setLockHoverId] = useState(null)
|
||||
const [undoHover, setUndoHover] = useState(false)
|
||||
const [pdfHover, setPdfHover] = useState(false)
|
||||
const [icsHover, setIcsHover] = useState(false)
|
||||
const [hoveredAssignmentId, setHoveredAssignmentId] = useState<number | null>(null)
|
||||
// Transit rows fold their itinerary out inline (#1065).
|
||||
@@ -984,8 +983,6 @@ function useDayPlanSidebar(props: DayPlanSidebarProps) {
|
||||
setLockHoverId,
|
||||
undoHover,
|
||||
setUndoHover,
|
||||
pdfHover,
|
||||
setPdfHover,
|
||||
icsHover,
|
||||
setIcsHover,
|
||||
hoveredAssignmentId,
|
||||
@@ -1155,8 +1152,6 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
|
||||
setLockHoverId,
|
||||
undoHover,
|
||||
setUndoHover,
|
||||
pdfHover,
|
||||
setPdfHover,
|
||||
icsHover,
|
||||
setIcsHover,
|
||||
hoveredAssignmentId,
|
||||
@@ -1267,8 +1262,6 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
|
||||
t={t}
|
||||
locale={locale}
|
||||
toast={toast}
|
||||
pdfHover={pdfHover}
|
||||
setPdfHover={setPdfHover}
|
||||
icsHover={icsHover}
|
||||
setIcsHover={setIcsHover}
|
||||
expandedDays={expandedDays}
|
||||
|
||||
@@ -46,8 +46,6 @@ function makeProps(overrides: Partial<React.ComponentProps<typeof DayPlanSidebar
|
||||
t,
|
||||
locale: 'en-US',
|
||||
toast: makeToast(),
|
||||
pdfHover: false,
|
||||
setPdfHover: vi.fn((_v: boolean) => {}),
|
||||
icsHover: false,
|
||||
setIcsHover: vi.fn((_v: boolean) => {}),
|
||||
expandedDays: new Set<number>(),
|
||||
@@ -97,21 +95,29 @@ describe('DayPlanSidebarToolbar', () => {
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('dayplan.pdfError: font missing'))
|
||||
})
|
||||
|
||||
it('FE-PLANNER-DPTOOLBAR-003: hovering the PDF button reports the hover state upward', async () => {
|
||||
it('FE-PLANNER-DPTOOLBAR-003: hovering the PDF button shows the export tooltip', async () => {
|
||||
const user = userEvent.setup()
|
||||
const setPdfHover = vi.fn((_v: boolean) => {})
|
||||
render(<DayPlanSidebarToolbar {...makeProps({ setPdfHover })} />)
|
||||
render(<DayPlanSidebarToolbar {...makeProps()} />)
|
||||
expect(screen.queryByText('dayplan.pdfTooltip')).not.toBeInTheDocument()
|
||||
await user.hover(screen.getByText('dayplan.pdf'))
|
||||
expect(setPdfHover).toHaveBeenCalledWith(true)
|
||||
await waitFor(() => expect(screen.getByText('dayplan.pdfTooltip')).toBeInTheDocument())
|
||||
await user.unhover(screen.getByText('dayplan.pdf'))
|
||||
expect(setPdfHover).toHaveBeenCalledWith(false)
|
||||
await waitFor(() => expect(screen.queryByText('dayplan.pdfTooltip')).not.toBeInTheDocument())
|
||||
})
|
||||
|
||||
it('FE-PLANNER-DPTOOLBAR-004: pdfHover renders the export tooltip', () => {
|
||||
const { rerender } = render(<DayPlanSidebarToolbar {...makeProps()} />)
|
||||
expect(screen.queryByText('dayplan.pdfTooltip')).not.toBeInTheDocument()
|
||||
rerender(<DayPlanSidebarToolbar {...makeProps({ pdfHover: true })} />)
|
||||
expect(screen.getByText('dayplan.pdfTooltip')).toBeInTheDocument()
|
||||
// The button sits at the right edge of the leftmost pane. Its own tooltip was
|
||||
// anchored with `right: 0` and never wrapped, so a long label ran off the left
|
||||
// of the window. The shared tooltip is portalled and clamped to the viewport.
|
||||
it('FE-PLANNER-DPTOOLBAR-004: the export tooltip stays inside the window', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<DayPlanSidebarToolbar {...makeProps()} />)
|
||||
await user.hover(screen.getByText('dayplan.pdf'))
|
||||
const tip = await screen.findByText('dayplan.pdfTooltip')
|
||||
expect(tip.closest('[role="tooltip"]')).not.toBeNull()
|
||||
expect(tip.parentElement).toBe(document.body)
|
||||
expect(tip.style.position).toBe('fixed')
|
||||
// Placed once measured, and never to the left of the window edge.
|
||||
await waitFor(() => expect(parseFloat(tip.style.left)).toBeGreaterThanOrEqual(0))
|
||||
})
|
||||
|
||||
// ── ICS menu ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -22,8 +22,6 @@ interface DayPlanSidebarToolbarProps {
|
||||
t: (key: string, params?: Record<string, any>) => string
|
||||
locale: string
|
||||
toast: ReturnType<typeof useToast>
|
||||
pdfHover: boolean
|
||||
setPdfHover: (v: boolean) => void
|
||||
icsHover: boolean
|
||||
setIcsHover: (v: boolean) => void
|
||||
expandedDays: Set<number>
|
||||
@@ -41,7 +39,7 @@ interface DayPlanSidebarToolbarProps {
|
||||
export function DayPlanSidebarToolbar({
|
||||
tripId, trip, days, places, categories, assignments, reservations, dayNotes,
|
||||
allConnectionsShown = false, onToggleAllConnections,
|
||||
t, locale, toast, pdfHover, setPdfHover, setIcsHover,
|
||||
t, locale, toast, setIcsHover,
|
||||
expandedDays, setExpandedDays, onUndo, canUndo, undoHover, setUndoHover, lastActionLabel,
|
||||
canEditDays, onReorderDays, onAddDay,
|
||||
}: DayPlanSidebarToolbarProps) {
|
||||
@@ -74,6 +72,7 @@ export function DayPlanSidebarToolbar({
|
||||
<div className="border-b border-edge-faint" style={{ padding: '12px 16px', flexShrink: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8 }}>
|
||||
<div style={{ position: 'relative', flexShrink: 0 }}>
|
||||
<Tooltip label={t('dayplan.pdfTooltip')} placement="bottom">
|
||||
<button
|
||||
onClick={async () => {
|
||||
const flatNotes = Object.entries(dayNotes).flatMap(([dayId, notes]) =>
|
||||
@@ -86,8 +85,6 @@ export function DayPlanSidebarToolbar({
|
||||
toast.error(t('dayplan.pdfError') + ': ' + (e?.message || String(e)))
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => setPdfHover(true)}
|
||||
onMouseLeave={() => setPdfHover(false)}
|
||||
className="bg-accent text-accent-text"
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: 5,
|
||||
@@ -99,18 +96,7 @@ export function DayPlanSidebarToolbar({
|
||||
<FileDown size={13} strokeWidth={2} />
|
||||
{t('dayplan.pdf')}
|
||||
</button>
|
||||
{pdfHover && (
|
||||
<div style={{
|
||||
position: 'absolute', top: 'calc(100% + 6px)', right: 0,
|
||||
whiteSpace: 'nowrap', pointerEvents: 'none', zIndex: 200,
|
||||
background: 'var(--bg-card, white)', color: 'var(--text-primary, #111827)',
|
||||
fontSize: 'calc(11px * var(--fs-scale-caption, 1))', fontWeight: 500, padding: '5px 10px',
|
||||
borderRadius: 8, boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||
border: '1px solid var(--border-faint, #e5e7eb)',
|
||||
}}>
|
||||
{t('dayplan.pdfTooltip')}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
style={{ position: 'relative', flexShrink: 0 }}
|
||||
|
||||
Reference in New Issue
Block a user