diff --git a/client/src/mobile/components/MUserPickerList.test.tsx b/client/src/mobile/components/MUserPickerList.test.tsx
new file mode 100644
index 000000000..cd2a9908f
--- /dev/null
+++ b/client/src/mobile/components/MUserPickerList.test.tsx
@@ -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();
+
+ 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();
+
+ 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();
+
+ fireEvent.click(screen.getByRole('button', { name: 'bob' }));
+
+ expect(onPick).toHaveBeenCalledWith(2);
+ });
+});
diff --git a/client/src/mobile/components/MUserPickerList.tsx b/client/src/mobile/components/MUserPickerList.tsx
new file mode 100644
index 000000000..d0ef6945a
--- /dev/null
+++ b/client/src/mobile/components/MUserPickerList.tsx
@@ -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 (
+
+ {users.map((u) => (
+
+ ))}
+
+ );
+}
diff --git a/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx b/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx
index 251f9f2b7..1f258d664 100644
--- a/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx
+++ b/client/src/mobile/screens/vacay/MVacayInviteSheet.tsx
@@ -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
- {pickerOpen && (
-
- {available.map(u => (
-
- ))}
-
- )}
+ {pickerOpen && }
>
)}
diff --git a/client/src/mobile/screens/vacay/MVacayShareSheet.tsx b/client/src/mobile/screens/vacay/MVacayShareSheet.tsx
index 712b917b5..b1ab621f0 100644
--- a/client/src/mobile/screens/vacay/MVacayShareSheet.tsx
+++ b/client/src/mobile/screens/vacay/MVacayShareSheet.tsx
@@ -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')}
- {pickerOpen && (
-
- {available.map(u => (
-
- ))}
-
- )}
+ {pickerOpen && }
>
)}