Compact tasks hero and harden sticky toolbar
This commit is contained in:
@@ -551,6 +551,12 @@ html.guest-theme.dark {
|
|||||||
animation: admin-fade-up 220ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
animation: admin-fade-up 220ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-sticky-toolbar {
|
||||||
|
position: sticky;
|
||||||
|
top: calc(env(safe-area-inset-top, 0px) + 76px);
|
||||||
|
z-index: 45;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.admin-fade-up {
|
.admin-fade-up {
|
||||||
animation: none;
|
animation: none;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RefreshCcw, Plus, Pencil, Trash2, ChevronDown, ChevronRight, Check } from 'lucide-react';
|
import { RefreshCcw, Plus, Pencil, Trash2, ChevronDown, ChevronRight, Check, Info } from 'lucide-react';
|
||||||
import { Card } from '@tamagui/card';
|
import { Card } from '@tamagui/card';
|
||||||
import { YStack, XStack } from '@tamagui/stacks';
|
import { YStack, XStack } from '@tamagui/stacks';
|
||||||
import { YGroup } from '@tamagui/group';
|
import { YGroup } from '@tamagui/group';
|
||||||
@@ -179,6 +179,7 @@ export default function MobileEventTasksPage() {
|
|||||||
const [emotionForm, setEmotionForm] = React.useState({ name: '', color: String(border) });
|
const [emotionForm, setEmotionForm] = React.useState({ name: '', color: String(border) });
|
||||||
const [savingEmotion, setSavingEmotion] = React.useState(false);
|
const [savingEmotion, setSavingEmotion] = React.useState(false);
|
||||||
const [showEmotionFilterSheet, setShowEmotionFilterSheet] = React.useState(false);
|
const [showEmotionFilterSheet, setShowEmotionFilterSheet] = React.useState(false);
|
||||||
|
const [showTaskDetails, setShowTaskDetails] = React.useState(false);
|
||||||
const [quickNavSelection, setQuickNavSelection] = React.useState<TaskSectionKey | ''>('');
|
const [quickNavSelection, setQuickNavSelection] = React.useState<TaskSectionKey | ''>('');
|
||||||
const [eventRecord, setEventRecord] = React.useState<TenantEvent | null>(null);
|
const [eventRecord, setEventRecord] = React.useState<TenantEvent | null>(null);
|
||||||
const [tasksToggleBusy, setTasksToggleBusy] = React.useState(false);
|
const [tasksToggleBusy, setTasksToggleBusy] = React.useState(false);
|
||||||
@@ -645,44 +646,67 @@ export default function MobileEventTasksPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
<MobileCard space="$3">
|
<MobileCard space="$2" padding="$3">
|
||||||
<YStack space="$1">
|
<XStack alignItems="center" justifyContent="space-between" space="$2" flexWrap="wrap">
|
||||||
<Text fontSize="$sm" fontWeight="800" color={text}>
|
<YStack space="$1" flex={1} minWidth={180}>
|
||||||
{t('events.tasks.toggle.title', 'Photo tasks for this event')}
|
<XStack alignItems="center" space="$2" flexWrap="wrap">
|
||||||
</Text>
|
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||||
<Text fontSize="$xs" color={muted}>
|
{t('events.tasks.toggle.title', 'Photo tasks for this event')}
|
||||||
{t(
|
</Text>
|
||||||
'events.tasks.toggle.description',
|
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
|
||||||
'Give guests optional photo tasks and prompts.'
|
{tasksEnabled
|
||||||
)}
|
? t('events.tasks.toggle.active', 'ACTIVE')
|
||||||
</Text>
|
: t('events.tasks.toggle.inactive', 'INACTIVE')}
|
||||||
</YStack>
|
</PillBadge>
|
||||||
<XStack alignItems="center" justifyContent="space-between" space="$3" flexWrap="wrap">
|
</XStack>
|
||||||
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
|
{showTaskDetails ? (
|
||||||
{tasksEnabled
|
<Text fontSize="$xs" color={muted}>
|
||||||
? t('events.tasks.toggle.active', 'ACTIVE')
|
{t(
|
||||||
: t('events.tasks.toggle.inactive', 'INACTIVE')}
|
'events.tasks.toggle.description',
|
||||||
</PillBadge>
|
'Give guests optional photo tasks and prompts.'
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
</YStack>
|
||||||
|
<XStack alignItems="center" space="$2">
|
||||||
|
<Pressable
|
||||||
|
onPress={() => setShowTaskDetails((prev) => !prev)}
|
||||||
|
aria-label={t(
|
||||||
|
'events.tasks.toggle.description',
|
||||||
|
'Give guests optional photo tasks and prompts.'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<XStack
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
borderRadius={10}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
borderWidth={1}
|
||||||
|
borderColor={showTaskDetails ? withAlpha(primary, 0.45) : border}
|
||||||
|
backgroundColor={showTaskDetails ? withAlpha(primary, 0.12) : surfaceMuted}
|
||||||
|
>
|
||||||
|
<Info size={14} color={showTaskDetails ? primary : muted} />
|
||||||
|
</XStack>
|
||||||
|
</Pressable>
|
||||||
|
<Switch
|
||||||
|
size="$4"
|
||||||
|
checked={tasksEnabled}
|
||||||
|
onCheckedChange={handleTasksToggle}
|
||||||
|
aria-label={t('events.tasks.toggle.switchLabel', 'Photo tasks enabled')}
|
||||||
|
disabled={!canManageTasks || tasksToggleBusy}
|
||||||
|
>
|
||||||
|
<Switch.Thumb />
|
||||||
|
</Switch>
|
||||||
|
</XStack>
|
||||||
|
</XStack>
|
||||||
|
{showTaskDetails ? (
|
||||||
<Text fontSize="$xs" color={muted}>
|
<Text fontSize="$xs" color={muted}>
|
||||||
{tasksEnabled
|
{tasksEnabled
|
||||||
? t('events.tasks.toggle.onLabel', 'Guests see photo tasks')
|
? t('events.tasks.toggle.onLabel', 'Guests see photo tasks')
|
||||||
: t('events.tasks.toggle.offLabel', 'Guest app shows photos only')}
|
: t('events.tasks.toggle.offLabel', 'Guest app shows photos only')}
|
||||||
</Text>
|
</Text>
|
||||||
</XStack>
|
) : null}
|
||||||
<XStack alignItems="center" justifyContent="space-between" marginTop="$2">
|
|
||||||
<Text fontSize="$xs" color={text} fontWeight="600">
|
|
||||||
{t('events.tasks.toggle.switchLabel', 'Photo tasks enabled')}
|
|
||||||
</Text>
|
|
||||||
<Switch
|
|
||||||
size="$4"
|
|
||||||
checked={tasksEnabled}
|
|
||||||
onCheckedChange={handleTasksToggle}
|
|
||||||
aria-label={t('events.tasks.toggle.switchLabel', 'Photo tasks enabled')}
|
|
||||||
disabled={!canManageTasks || tasksToggleBusy}
|
|
||||||
>
|
|
||||||
<Switch.Thumb />
|
|
||||||
</Switch>
|
|
||||||
</XStack>
|
|
||||||
{isMember && !canManageTasks ? (
|
{isMember && !canManageTasks ? (
|
||||||
<Text fontSize="$xs" color={muted}>
|
<Text fontSize="$xs" color={muted}>
|
||||||
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change photo tasks.')}
|
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change photo tasks.')}
|
||||||
@@ -750,12 +774,7 @@ export default function MobileEventTasksPage() {
|
|||||||
</YStack>
|
</YStack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<YStack
|
<YStack className="admin-sticky-toolbar" width="100%">
|
||||||
position="sticky"
|
|
||||||
zIndex={45}
|
|
||||||
width="100%"
|
|
||||||
style={{ top: 'calc(env(safe-area-inset-top, 0px) + 76px)' }}
|
|
||||||
>
|
|
||||||
<Card
|
<Card
|
||||||
borderRadius={20}
|
borderRadius={20}
|
||||||
borderWidth={2}
|
borderWidth={2}
|
||||||
|
|||||||
@@ -156,8 +156,16 @@ vi.mock('@tamagui/list-item', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@tamagui/react-native-web-lite', () => ({
|
vi.mock('@tamagui/react-native-web-lite', () => ({
|
||||||
Pressable: ({ children, onPress }: { children: React.ReactNode; onPress?: () => void }) => (
|
Pressable: ({
|
||||||
<button type="button" onClick={onPress}>
|
children,
|
||||||
|
onPress,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
onPress?: () => void;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}) => (
|
||||||
|
<button type="button" onClick={onPress} {...rest}>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
),
|
),
|
||||||
@@ -278,6 +286,17 @@ describe('MobileEventTasksPage', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('toggles task details in the hero section', async () => {
|
||||||
|
render(<MobileEventTasksPage />);
|
||||||
|
|
||||||
|
const detailsLabel = 'Give guests optional photo tasks and prompts.';
|
||||||
|
expect(await screen.findByText('Photo tasks for this event')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(detailsLabel)).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByLabelText(detailsLabel));
|
||||||
|
expect(screen.getByText(detailsLabel)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('enters selection mode on long press', async () => {
|
it('enters selection mode on long press', async () => {
|
||||||
render(<MobileEventTasksPage />);
|
render(<MobileEventTasksPage />);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user