Fix sticky tasks toolbar layout
This commit is contained in:
@@ -555,6 +555,7 @@ html.guest-theme.dark {
|
||||
position: sticky;
|
||||
top: calc(env(safe-area-inset-top, 0px) + 76px);
|
||||
z-index: 45;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
@@ -618,219 +618,7 @@ export default function MobileEventTasksPage() {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileShell
|
||||
activeTab="tasks"
|
||||
title={t('events.tasks.title', 'Photo tasks & checklists')}
|
||||
onBack={back}
|
||||
headerActions={
|
||||
<XStack space="$2">
|
||||
<HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
|
||||
<RefreshCcw size={18} color={text} />
|
||||
</HeaderActionButton>
|
||||
</XStack>
|
||||
}
|
||||
>
|
||||
{error ? (
|
||||
<MobileCard>
|
||||
<Text fontSize={13} fontWeight="600" color={danger}>
|
||||
{error}
|
||||
</Text>
|
||||
<CTAButton
|
||||
label={t('common.retry', 'Retry')}
|
||||
tone="ghost"
|
||||
fullWidth={false}
|
||||
onPress={() => load()}
|
||||
/>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
<MobileCard space="$2" padding="$3">
|
||||
<XStack alignItems="center" justifyContent="space-between" space="$2" flexWrap="wrap">
|
||||
<YStack space="$1" flex={1} minWidth={180}>
|
||||
<XStack alignItems="center" space="$2" flexWrap="wrap">
|
||||
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||
{t('events.tasks.toggle.title', 'Photo tasks for this event')}
|
||||
</Text>
|
||||
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
|
||||
{tasksEnabled
|
||||
? t('events.tasks.toggle.active', 'ACTIVE')
|
||||
: t('events.tasks.toggle.inactive', 'INACTIVE')}
|
||||
</PillBadge>
|
||||
</XStack>
|
||||
{showTaskDetails ? (
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t(
|
||||
'events.tasks.toggle.description',
|
||||
'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}>
|
||||
{tasksEnabled
|
||||
? t('events.tasks.toggle.onLabel', 'Guests see photo tasks')
|
||||
: t('events.tasks.toggle.offLabel', 'Guest app shows photos only')}
|
||||
</Text>
|
||||
) : null}
|
||||
{isMember && !canManageTasks ? (
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change photo tasks.')}
|
||||
</Text>
|
||||
) : null}
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
<YStack space="$2">
|
||||
<Card
|
||||
borderRadius={22}
|
||||
borderWidth={2}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
padding="$3"
|
||||
>
|
||||
<YStack space="$2.5">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<XStack
|
||||
alignItems="center"
|
||||
paddingHorizontal="$3"
|
||||
paddingVertical="$1.5"
|
||||
borderRadius={999}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
>
|
||||
<Text fontSize="$xs" fontWeight="800" color={text}>
|
||||
{t('events.tasks.quickNav', 'Quick jump')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={quickNavSelection}
|
||||
onValueChange={(next) => {
|
||||
const key = next as TaskSectionKey | '';
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
setQuickNavSelection(key);
|
||||
handleQuickNav(key);
|
||||
}}
|
||||
>
|
||||
<XStack space="$2" paddingVertical="$1">
|
||||
{sectionCounts.map((section) => (
|
||||
<QuickNavChip
|
||||
key={section.key}
|
||||
value={section.key}
|
||||
label={t(`events.tasks.sections.${section.key}`, section.key)}
|
||||
count={section.count}
|
||||
onPress={() => {
|
||||
setQuickNavSelection(section.key);
|
||||
handleQuickNav(section.key);
|
||||
}}
|
||||
isActive={quickNavSelection === section.key}
|
||||
/>
|
||||
))}
|
||||
</XStack>
|
||||
</ToggleGroup>
|
||||
</ScrollView>
|
||||
</YStack>
|
||||
</Card>
|
||||
|
||||
<YStack className="admin-sticky-toolbar" width="100%">
|
||||
<Card
|
||||
borderRadius={20}
|
||||
borderWidth={2}
|
||||
borderColor={stickyBorder}
|
||||
backgroundColor={stickySurface}
|
||||
padding="$3"
|
||||
shadowColor={stickyShadow}
|
||||
shadowOpacity={0.16}
|
||||
shadowRadius={16}
|
||||
shadowOffset={{ width: 0, height: 10 }}
|
||||
>
|
||||
<XStack alignItems="center" space="$2">
|
||||
<XStack flex={1}>
|
||||
<MobileInput
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('events.tasks.search', 'Search photo tasks')}
|
||||
compact
|
||||
/>
|
||||
</XStack>
|
||||
<Pressable onPress={() => setShowEmotionFilterSheet(true)}>
|
||||
<XStack
|
||||
alignItems="center"
|
||||
space="$1.5"
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
borderRadius={14}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
>
|
||||
<Text fontSize={11} fontWeight="700" color={text}>
|
||||
{t('events.tasks.emotionFilterShort', 'Emotion')}
|
||||
</Text>
|
||||
<Text fontSize={11} color={muted}>
|
||||
{emotionFilter
|
||||
? emotions.find((e) => String(e.id) === emotionFilter)?.name ?? t('events.tasks.customEmotion', 'Custom emotion')
|
||||
: t('events.tasks.allEmotions', 'All')}
|
||||
</Text>
|
||||
<ChevronDown size={14} color={muted} />
|
||||
</XStack>
|
||||
</Pressable>
|
||||
</XStack>
|
||||
</Card>
|
||||
</YStack>
|
||||
</YStack>
|
||||
) : null}
|
||||
|
||||
{loading ? (
|
||||
<YStack space="$2">
|
||||
{Array.from({ length: 4 }).map((_, idx) => (
|
||||
<SkeletonCard key={`tsk-${idx}`} height={70} />
|
||||
))}
|
||||
</YStack>
|
||||
) : assignedTasks.length === 0 ? (
|
||||
const taskPanel = assignedTasks.length === 0 ? (
|
||||
<YStack space="$2">
|
||||
<MobileCard space="$2">
|
||||
<Text fontSize={13} fontWeight="700" color={text}>
|
||||
@@ -1076,6 +864,220 @@ export default function MobileEventTasksPage() {
|
||||
</YGroup>
|
||||
)}
|
||||
</YStack>
|
||||
);
|
||||
|
||||
return (
|
||||
<MobileShell
|
||||
activeTab="tasks"
|
||||
title={t('events.tasks.title', 'Photo tasks & checklists')}
|
||||
onBack={back}
|
||||
headerActions={
|
||||
<XStack space="$2">
|
||||
<HeaderActionButton onPress={() => load()} ariaLabel={t('common.refresh', 'Refresh')}>
|
||||
<RefreshCcw size={18} color={text} />
|
||||
</HeaderActionButton>
|
||||
</XStack>
|
||||
}
|
||||
>
|
||||
{error ? (
|
||||
<MobileCard>
|
||||
<Text fontSize={13} fontWeight="600" color={danger}>
|
||||
{error}
|
||||
</Text>
|
||||
<CTAButton
|
||||
label={t('common.retry', 'Retry')}
|
||||
tone="ghost"
|
||||
fullWidth={false}
|
||||
onPress={() => load()}
|
||||
/>
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
<MobileCard space="$2" padding="$3">
|
||||
<XStack alignItems="center" justifyContent="space-between" space="$2" flexWrap="wrap">
|
||||
<YStack space="$1" flex={1} minWidth={180}>
|
||||
<XStack alignItems="center" space="$2" flexWrap="wrap">
|
||||
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||
{t('events.tasks.toggle.title', 'Photo tasks for this event')}
|
||||
</Text>
|
||||
<PillBadge tone={tasksEnabled ? 'success' : 'warning'}>
|
||||
{tasksEnabled
|
||||
? t('events.tasks.toggle.active', 'ACTIVE')
|
||||
: t('events.tasks.toggle.inactive', 'INACTIVE')}
|
||||
</PillBadge>
|
||||
</XStack>
|
||||
{showTaskDetails ? (
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t(
|
||||
'events.tasks.toggle.description',
|
||||
'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}>
|
||||
{tasksEnabled
|
||||
? t('events.tasks.toggle.onLabel', 'Guests see photo tasks')
|
||||
: t('events.tasks.toggle.offLabel', 'Guest app shows photos only')}
|
||||
</Text>
|
||||
) : null}
|
||||
{isMember && !canManageTasks ? (
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change photo tasks.')}
|
||||
</Text>
|
||||
) : null}
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
{loading ? (
|
||||
<YStack space="$2">
|
||||
{Array.from({ length: 4 }).map((_, idx) => (
|
||||
<SkeletonCard key={`tsk-${idx}`} height={70} />
|
||||
))}
|
||||
</YStack>
|
||||
) : (
|
||||
<YStack space="$2">
|
||||
<Card
|
||||
borderRadius={22}
|
||||
borderWidth={2}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
padding="$3"
|
||||
>
|
||||
<YStack space="$2.5">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<XStack
|
||||
alignItems="center"
|
||||
paddingHorizontal="$3"
|
||||
paddingVertical="$1.5"
|
||||
borderRadius={999}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
>
|
||||
<Text fontSize="$xs" fontWeight="800" color={text}>
|
||||
{t('events.tasks.quickNav', 'Quick jump')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={quickNavSelection}
|
||||
onValueChange={(next) => {
|
||||
const key = next as TaskSectionKey | '';
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
setQuickNavSelection(key);
|
||||
handleQuickNav(key);
|
||||
}}
|
||||
>
|
||||
<XStack space="$2" paddingVertical="$1">
|
||||
{sectionCounts.map((section) => (
|
||||
<QuickNavChip
|
||||
key={section.key}
|
||||
value={section.key}
|
||||
label={t(`events.tasks.sections.${section.key}`, section.key)}
|
||||
count={section.count}
|
||||
onPress={() => {
|
||||
setQuickNavSelection(section.key);
|
||||
handleQuickNav(section.key);
|
||||
}}
|
||||
isActive={quickNavSelection === section.key}
|
||||
/>
|
||||
))}
|
||||
</XStack>
|
||||
</ToggleGroup>
|
||||
</ScrollView>
|
||||
</YStack>
|
||||
</Card>
|
||||
|
||||
<div className="admin-sticky-toolbar">
|
||||
<Card
|
||||
borderRadius={20}
|
||||
borderWidth={2}
|
||||
borderColor={stickyBorder}
|
||||
backgroundColor={stickySurface}
|
||||
padding="$3"
|
||||
shadowColor={stickyShadow}
|
||||
shadowOpacity={0.16}
|
||||
shadowRadius={16}
|
||||
shadowOffset={{ width: 0, height: 10 }}
|
||||
>
|
||||
<XStack alignItems="center" space="$2">
|
||||
<XStack flex={1}>
|
||||
<MobileInput
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('events.tasks.search', 'Search photo tasks')}
|
||||
compact
|
||||
/>
|
||||
</XStack>
|
||||
<Pressable onPress={() => setShowEmotionFilterSheet(true)}>
|
||||
<XStack
|
||||
alignItems="center"
|
||||
space="$1.5"
|
||||
paddingVertical="$2"
|
||||
paddingHorizontal="$3"
|
||||
borderRadius={14}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
>
|
||||
<Text fontSize={11} fontWeight="700" color={text}>
|
||||
{t('events.tasks.emotionFilterShort', 'Emotion')}
|
||||
</Text>
|
||||
<Text fontSize={11} color={muted}>
|
||||
{emotionFilter
|
||||
? emotions.find((e) => String(e.id) === emotionFilter)?.name ?? t('events.tasks.customEmotion', 'Custom emotion')
|
||||
: t('events.tasks.allEmotions', 'All')}
|
||||
</Text>
|
||||
<ChevronDown size={14} color={muted} />
|
||||
</XStack>
|
||||
</Pressable>
|
||||
</XStack>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{taskPanel}
|
||||
</YStack>
|
||||
)}
|
||||
|
||||
<MobileSheet
|
||||
|
||||
Reference in New Issue
Block a user