From c8d8ddfd3e2fbf9b74147016d9caa9a8a67ad2f9 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 9 Oct 2026 15:01:55 +0200 Subject: [PATCH] refactor(packing): share the template row of the two apply template menus ApplyTemplateButton and the packing list header drew the same template row in their menus. PackingTemplateOption now draws it; the header's faded hover stays a parameter (fade). --- .../Packing/ApplyTemplateButton.tsx | 19 +------- .../Packing/PackingListPanelHeader.tsx | 25 ++++------ .../Packing/PackingTemplateOption.test.tsx | 38 +++++++++++++++ .../Packing/PackingTemplateOption.tsx | 46 +++++++++++++++++++ 4 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 client/src/components/Packing/PackingTemplateOption.test.tsx create mode 100644 client/src/components/Packing/PackingTemplateOption.tsx diff --git a/client/src/components/Packing/ApplyTemplateButton.tsx b/client/src/components/Packing/ApplyTemplateButton.tsx index 70bd13187..239137927 100644 --- a/client/src/components/Packing/ApplyTemplateButton.tsx +++ b/client/src/components/Packing/ApplyTemplateButton.tsx @@ -4,6 +4,7 @@ import { packingApi } from '../../api/client' import { useTripStore } from '../../store/tripStore' import { useToast } from '../shared/Toast' import { useTranslation } from '../../i18n' +import { PackingTemplateOption } from './PackingTemplateOption' interface Template { id: number @@ -79,23 +80,7 @@ export default function ApplyTemplateButton({ tripId, visibility, style, classNa }} > {templates.map(tmpl => ( - + handleApply(tmpl.id)} t={t} /> ))} )} diff --git a/client/src/components/Packing/PackingListPanelHeader.tsx b/client/src/components/Packing/PackingListPanelHeader.tsx index aa11c5b86..d445a2eab 100644 --- a/client/src/components/Packing/PackingListPanelHeader.tsx +++ b/client/src/components/Packing/PackingListPanelHeader.tsx @@ -3,6 +3,7 @@ import { } from 'lucide-react' import type { PackingState } from './usePackingListPanel' import NameDialog from '../shared/NameDialog' +import { PackingTemplateOption } from './PackingTemplateOption' export function PackingHeader(S: PackingState) { const { @@ -63,22 +64,14 @@ export function PackingHeader(S: PackingState) { boxShadow: '0 4px 16px rgba(0,0,0,0.12)', padding: 4, minWidth: 200, }}> {availableTemplates.map(tmpl => ( - + handleApplyTemplate(tmpl.id)} + t={t} + fade + /> ))} )} diff --git a/client/src/components/Packing/PackingTemplateOption.test.tsx b/client/src/components/Packing/PackingTemplateOption.test.tsx new file mode 100644 index 000000000..17eddb1e4 --- /dev/null +++ b/client/src/components/Packing/PackingTemplateOption.test.tsx @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from 'vitest'; +import { fireEvent, render, screen } from '../../../tests/helpers/render'; +import { PackingTemplateOption } from './PackingTemplateOption'; + +const t = (key: string, params?: Record) => + key === 'admin.packingTemplates.items' ? `items(${params?.count})` : key; + +describe('PackingTemplateOption', () => { + it('shows the template name over its item count', () => { + render(); + expect(screen.getByText('Beach')).toBeInTheDocument(); + expect(screen.getByText('12 items(12)')).toBeInTheDocument(); + }); + + it('applies the template on click', () => { + const onClick = vi.fn(); + render(); + fireEvent.click(screen.getByRole('button')); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it('tints the row while hovered and clears it on leave', () => { + render(); + const row = screen.getByRole('button'); + fireEvent.mouseEnter(row); + expect(row.style.background).toBe('var(--bg-tertiary)'); + fireEvent.mouseLeave(row); + expect(row.style.background).toBe('transparent'); + }); + + it('fades the hover only when asked', () => { + const { unmount } = render(); + expect(screen.getByRole('button').style.transition).toBe(''); + unmount(); + render(); + expect(screen.getByRole('button').style.transition).toBe('background 0.1s'); + }); +}); diff --git a/client/src/components/Packing/PackingTemplateOption.tsx b/client/src/components/Packing/PackingTemplateOption.tsx new file mode 100644 index 000000000..2b9cd6e75 --- /dev/null +++ b/client/src/components/Packing/PackingTemplateOption.tsx @@ -0,0 +1,46 @@ +import { Package } from 'lucide-react'; +import type { TranslationFn } from '../../types'; + +interface PackingTemplateOptionProps { + name: string; + itemCount: number; + onClick: () => void; + t: TranslationFn; + /** Fade the hover background in rather than switching it. */ + fade?: boolean; +} + +/** One packing template in an apply menu: its name over how many items it adds. */ +export function PackingTemplateOption({ name, itemCount, onClick, t, fade = false }: PackingTemplateOptionProps) { + return ( + + ); +}