mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
feat(todo): To-Do in the look of the packing list, detail pane always in reach
Rows get the packing list's filled checkbox, priority, due date and list as quiet badges, and the assignee as an avatar with the name as its tooltip. The tasks sit in a card whose head names the filter and holds the sort, Priority or Due date, which moved out of the sidebar; a second click on the active one goes back to the manual order. The progress card matches the packing list's. The detail pane is a card with a box in its head that ticks the task off, and Delete and Save changes stay in sight: the To-Do view now fills the space under the Lists bar instead of a fixed height that pushed the pane's foot off screen. The detail pane and the new-task form share one set of fields, and a new list is named in a dialog, from the sidebar and from the form; the dialog moved to shared/NameDialog for both lists. The To-Do help pictures are re-rendered, the texts that described the old sidebar and form follow in all 27 languages, and the wiki page is updated. The phone screens are unchanged.
This commit is contained in:
@@ -2,7 +2,7 @@ import {
|
||||
CheckCheck, Luggage, Package, FolderPlus, Download, Trash2,
|
||||
} from 'lucide-react'
|
||||
import type { PackingState } from './usePackingListPanel'
|
||||
import PackingNameDialog from './PackingNameDialog'
|
||||
import NameDialog from '../shared/NameDialog'
|
||||
|
||||
export function PackingHeader(S: PackingState) {
|
||||
const {
|
||||
@@ -199,7 +199,7 @@ export function PackingHeader(S: PackingState) {
|
||||
|
||||
{/* Naming a new list or a template happens in a dialog of its own, not in a
|
||||
field pushed into the page. */}
|
||||
<PackingNameDialog
|
||||
<NameDialog
|
||||
open={canEdit && addingCategory}
|
||||
title={t('packing.addCategory')}
|
||||
placeholder={t('packing.newCategoryPlaceholder')}
|
||||
@@ -209,7 +209,7 @@ export function PackingHeader(S: PackingState) {
|
||||
onConfirm={handleAddNewCategory}
|
||||
onClose={() => { setAddingCategory(false); setNewCatName('') }}
|
||||
/>
|
||||
<PackingNameDialog
|
||||
<NameDialog
|
||||
open={canEdit && isAdmin && items.length > 0 && showSaveTemplate}
|
||||
title={t('packing.saveAsTemplate')}
|
||||
placeholder={t('packing.templateName')}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
// FE-PACKNAME-001 to FE-PACKNAME-004
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '../../../tests/helpers/render'
|
||||
import PackingNameDialog from './PackingNameDialog'
|
||||
|
||||
function setup(value: string, open = true) {
|
||||
const onChange = vi.fn()
|
||||
const onConfirm = vi.fn()
|
||||
const onClose = vi.fn()
|
||||
render(
|
||||
<PackingNameDialog open={open} title="Add list" placeholder="List name" confirmLabel="Add"
|
||||
value={value} onChange={onChange} onConfirm={onConfirm} onClose={onClose} />,
|
||||
)
|
||||
return { onChange, onConfirm, onClose }
|
||||
}
|
||||
|
||||
describe('PackingNameDialog', () => {
|
||||
it('FE-PACKNAME-001: asks for the name under its title, confirm waiting for one', () => {
|
||||
const { onChange } = setup('')
|
||||
expect(screen.getByText('Add list')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'Add' })).toBeDisabled()
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText('List name'), { target: { value: 'Rain kit' } })
|
||||
expect(onChange).toHaveBeenCalledWith('Rain kit')
|
||||
})
|
||||
|
||||
it('FE-PACKNAME-002: Enter and the button confirm a typed name', () => {
|
||||
const { onConfirm } = setup('Rain kit')
|
||||
fireEvent.keyDown(screen.getByPlaceholderText('List name'), { key: 'Enter' })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add' }))
|
||||
expect(onConfirm).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('FE-PACKNAME-003: Enter on a blank name does nothing, Cancel closes', () => {
|
||||
const { onConfirm, onClose } = setup(' ')
|
||||
fireEvent.keyDown(screen.getByPlaceholderText('List name'), { key: 'Enter' })
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('FE-PACKNAME-004: stays out of the page while closed', () => {
|
||||
setup('', false)
|
||||
expect(screen.queryByPlaceholderText('List name')).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,53 +0,0 @@
|
||||
import Modal from '../shared/Modal'
|
||||
import { useTranslation } from '../../i18n'
|
||||
|
||||
interface PackingNameDialogProps {
|
||||
open: boolean
|
||||
title: string
|
||||
placeholder: string
|
||||
confirmLabel: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
onConfirm: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* The small dialog that asks for one name: a new list, a template to save.
|
||||
* It takes the name in the middle of the screen instead of pushing a field
|
||||
* into the page; Enter confirms and Esc (from the modal) cancels.
|
||||
*/
|
||||
export default function PackingNameDialog({ open, title, placeholder, confirmLabel, value, onChange, onConfirm, onClose }: PackingNameDialogProps) {
|
||||
const { t } = useTranslation()
|
||||
const ready = value.trim().length > 0
|
||||
return (
|
||||
<Modal
|
||||
isOpen={open}
|
||||
onClose={onClose}
|
||||
title={title}
|
||||
size="sm"
|
||||
footer={(
|
||||
<div className="flex justify-end gap-2">
|
||||
<button type="button" onClick={onClose} className="rounded-lg border border-edge px-4 py-2 text-body text-content-muted hover:bg-surface-hover">
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
<button type="button" onClick={onConfirm} disabled={!ready}
|
||||
className="rounded-lg bg-accent px-4 py-2 text-body font-medium text-accent-text hover:bg-accent-hover disabled:cursor-default disabled:opacity-40">
|
||||
{confirmLabel}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter' && ready) onConfirm() }}
|
||||
placeholder={placeholder}
|
||||
aria-label={title}
|
||||
className="w-full rounded-lg border border-edge bg-surface-input px-3 py-2.5 text-body text-content outline-none focus:border-content-muted"
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user