mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +00:00
refactor(vacay): draw the phone user picker list with one component
The invite and the share sheet each carried the same unfolded user list. Both now render MUserPickerList with the users, the picked id and the pick handler from useVacayUserPicker. The markup is unchanged.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// FE-COMP-MUSERPICK-001 to FE-COMP-MUSERPICK-003
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import MUserPickerList from './MUserPickerList';
|
||||
|
||||
const users = [
|
||||
{ id: 2, username: 'bob' },
|
||||
{ id: 3, username: 'carol' },
|
||||
];
|
||||
|
||||
describe('MUserPickerList', () => {
|
||||
it('FE-COMP-MUSERPICK-001: renders one button per user and tints none while nothing is picked', () => {
|
||||
render(<MUserPickerList users={users} selected={null} onPick={vi.fn()} />);
|
||||
|
||||
const rows = screen.getAllByRole('button');
|
||||
expect(rows.map((b) => b.textContent)).toEqual(['bob', 'carol']);
|
||||
for (const row of rows) {
|
||||
expect(row).toHaveAttribute('type', 'button');
|
||||
expect(row.className).not.toContain('bg-[color:var(--m-ic)]');
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-COMP-MUSERPICK-002: tints only the picked user', () => {
|
||||
render(<MUserPickerList users={users} selected={3} onPick={vi.fn()} />);
|
||||
|
||||
expect(screen.getByRole('button', { name: 'carol' }).className).toContain('bg-[color:var(--m-ic)]');
|
||||
expect(screen.getByRole('button', { name: 'bob' }).className).not.toContain('bg-[color:var(--m-ic)]');
|
||||
});
|
||||
|
||||
it('FE-COMP-MUSERPICK-003: hands the tapped user id to onPick', () => {
|
||||
const onPick = vi.fn();
|
||||
render(<MUserPickerList users={users} selected={null} onPick={onPick} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'bob' }));
|
||||
|
||||
expect(onPick).toHaveBeenCalledWith(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
interface MUserPickerListProps {
|
||||
users: { id: number; username: string }[];
|
||||
selected: number | null;
|
||||
onPick: (id: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unfolded user list under a phone sheet's user picker (the vacay invite
|
||||
* and share sheets): one row per user, the picked one tinted.
|
||||
*/
|
||||
export default function MUserPickerList({ users, selected, onPick }: MUserPickerListProps) {
|
||||
return (
|
||||
<div className="mt-[6px] max-h-[180px] overflow-y-auto rounded-[14px] border border-[color:var(--m-rowbr)] bg-[color:var(--m-sheetop)] p-[6px]">
|
||||
{users.map((u) => (
|
||||
<button
|
||||
key={u.id}
|
||||
type="button"
|
||||
onClick={() => onPick(u.id)}
|
||||
className={`flex w-full items-center gap-[9px] rounded-[10px] px-[10px] py-[9px] text-start text-[0.8125rem] font-semibold ${
|
||||
u.id === selected ? 'bg-[color:var(--m-ic)]' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{u.username}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChevronDown, Clock, Loader2, X } from 'lucide-react'
|
||||
import MSheet from '../../components/MSheet'
|
||||
import MIconBtn from '../../components/MIconBtn'
|
||||
import MUserPickerList from '../../components/MUserPickerList'
|
||||
import { useVacayStore } from '../../../store/vacayStore'
|
||||
import { useTranslation } from '../../../i18n'
|
||||
import { type VacayUser } from '../../../types'
|
||||
@@ -59,22 +60,7 @@ export default function MVacayInviteSheet({ open, onClose }: MVacayInviteSheetPr
|
||||
</span>
|
||||
<ChevronDown size={14} strokeWidth={2} className="flex-none text-m-faint" />
|
||||
</button>
|
||||
{pickerOpen && (
|
||||
<div className="mt-[6px] max-h-[180px] overflow-y-auto rounded-[14px] border border-[color:var(--m-rowbr)] bg-[color:var(--m-sheetop)] p-[6px]">
|
||||
{available.map(u => (
|
||||
<button
|
||||
key={u.id}
|
||||
type="button"
|
||||
onClick={() => pick(u.id)}
|
||||
className={`flex w-full items-center gap-[9px] rounded-[10px] px-[10px] py-[9px] text-start text-[0.8125rem] font-semibold ${
|
||||
u.id === selected ? 'bg-[color:var(--m-ic)]' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{u.username}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{pickerOpen && <MUserPickerList users={available} selected={selected} onPick={pick} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChevronDown, Eye, EyeOff, Loader2, Share2, X } from 'lucide-react'
|
||||
import MSheet from '../../components/MSheet'
|
||||
import MIconBtn from '../../components/MIconBtn'
|
||||
import MUserPickerList from '../../components/MUserPickerList'
|
||||
import { useVacayStore } from '../../../store/vacayStore'
|
||||
import { useTranslation } from '../../../i18n'
|
||||
import { useVacayShareActions } from '../../../components/Vacay/useVacayShareActions'
|
||||
@@ -71,22 +72,7 @@ export default function MVacayShareSheet({ open, onClose }: MVacayShareSheetProp
|
||||
{t('vacay.share')}
|
||||
</button>
|
||||
</div>
|
||||
{pickerOpen && (
|
||||
<div className="mt-[6px] max-h-[180px] overflow-y-auto rounded-[14px] border border-[color:var(--m-rowbr)] bg-[color:var(--m-sheetop)] p-[6px]">
|
||||
{available.map(u => (
|
||||
<button
|
||||
key={u.id}
|
||||
type="button"
|
||||
onClick={() => pick(u.id)}
|
||||
className={`flex w-full items-center gap-[9px] rounded-[10px] px-[10px] py-[9px] text-start text-[0.8125rem] font-semibold ${
|
||||
u.id === selected ? 'bg-[color:var(--m-ic)]' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{u.username}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{pickerOpen && <MUserPickerList users={available} selected={selected} onPick={pick} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user