Shrink control room photo actions
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-20 14:02:49 +01:00
parent 5674ed99f1
commit e5e74febbd
2 changed files with 45 additions and 20 deletions

View File

@@ -93,6 +93,8 @@ type PhotoGridAction = {
icon: React.ComponentType<{ size?: number; color?: string }>;
onPress: () => void;
disabled?: boolean;
backgroundColor?: string;
iconColor?: string;
};
function PhotoGrid({
@@ -108,8 +110,8 @@ function PhotoGrid({
}) {
const gridStyle: React.CSSProperties = {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
gap: 10,
gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))',
gap: 12,
};
return (
@@ -139,8 +141,8 @@ function PhotoGridTile({
isBusy: boolean;
}) {
const { border, muted, surfaceMuted } = useAdminTheme();
const overlayBg = withAlpha('#0f172a', 0.65);
const actionBg = withAlpha('#ffffff', 0.14);
const overlayBg = withAlpha('#0f172a', 0.55);
const actionBg = withAlpha('#0f172a', 0.55);
return (
<div
@@ -184,7 +186,8 @@ function PhotoGridTile({
padding="$1"
borderRadius={12}
backgroundColor={overlayBg}
space="$1.5"
space="$2"
justifyContent="space-between"
>
{actions.map((action) => (
<PhotoActionButton
@@ -193,7 +196,8 @@ function PhotoGridTile({
icon={action.icon}
onPress={action.onPress}
disabled={action.disabled}
backgroundColor={actionBg}
backgroundColor={action.backgroundColor ?? actionBg}
iconColor={action.iconColor ?? '#fff'}
/>
))}
</XStack>
@@ -207,32 +211,32 @@ function PhotoActionButton({
onPress,
disabled = false,
backgroundColor,
iconColor,
}: {
label: string;
icon: React.ComponentType<{ size?: number; color?: string }>;
onPress: () => void;
disabled?: boolean;
backgroundColor: string;
iconColor: string;
}) {
return (
<Pressable
onPress={disabled ? undefined : onPress}
disabled={disabled}
aria-label={label}
title={label}
style={{ flex: 1, opacity: disabled ? 0.55 : 1 }}
>
<YStack
alignItems="center"
justifyContent="center"
paddingVertical={6}
paddingHorizontal={4}
borderRadius={10}
minHeight={36}
paddingVertical={8}
borderRadius={12}
minHeight={40}
style={{ backgroundColor }}
>
<Icon size={14} color="#fff" />
<Text fontSize={9} fontWeight="700" color="#fff" textAlign="center">
{label}
</Text>
<Icon size={18} color={iconColor} />
</YStack>
</Pressable>
);
@@ -258,7 +262,7 @@ export default function MobileEventControlRoomPage() {
const isMember = user?.role === 'member';
const slug = slugParam ?? activeEvent?.slug ?? null;
const online = useOnlineStatus();
const { textStrong, text, muted, border, accentSoft, accent, danger } = useAdminTheme();
const { textStrong, text, muted, border, accentSoft, accent, danger, primary } = useAdminTheme();
const [activeTab, setActiveTab] = React.useState<'moderation' | 'live'>('moderation');
const [moderationPhotos, setModerationPhotos] = React.useState<TenantPhoto[]>([]);
@@ -903,6 +907,9 @@ export default function MobileEventControlRoomPage() {
const featureLabel = photo.is_featured
? t('photos.actions.unfeature', 'Remove highlight')
: t('photos.actions.feature', 'Set highlight');
const approveBg = withAlpha(primary, 0.78);
const hideBg = withAlpha(danger, 0.75);
const highlightBg = photo.is_featured ? withAlpha(accent, 0.85) : withAlpha(accent, 0.55);
return [
{
key: 'approve',
@@ -910,6 +917,7 @@ export default function MobileEventControlRoomPage() {
icon: Check,
onPress: () => handleModerationAction('approve', photo),
disabled: !canApprove || isBusy,
backgroundColor: approveBg,
},
{
key: 'visibility',
@@ -917,6 +925,7 @@ export default function MobileEventControlRoomPage() {
icon: canShow ? Eye : EyeOff,
onPress: () => handleModerationAction(visibilityAction, photo),
disabled: isBusy,
backgroundColor: hideBg,
},
{
key: 'feature',
@@ -924,6 +933,7 @@ export default function MobileEventControlRoomPage() {
icon: Sparkles,
onPress: () => handleModerationAction(featureAction, photo),
disabled: isBusy,
backgroundColor: highlightBg,
},
];
}}
@@ -1023,6 +1033,9 @@ export default function MobileEventControlRoomPage() {
? t('photos.actions.unfeature', 'Remove highlight')
: t('photos.actions.feature', 'Set highlight');
const approveBg = withAlpha(primary, 0.78);
const hideBg = withAlpha(danger, 0.75);
const highlightBg = photo.is_featured ? withAlpha(accent, 0.85) : withAlpha(accent, 0.55);
return [
{
key: 'approve',
@@ -1038,6 +1051,7 @@ export default function MobileEventControlRoomPage() {
}
},
disabled: approveDisabled,
backgroundColor: approveBg,
},
{
key: 'visibility',
@@ -1051,6 +1065,7 @@ export default function MobileEventControlRoomPage() {
void handleReject(photo);
},
disabled: !online || isLiveBusy || isModerationBusy,
backgroundColor: hideBg,
},
{
key: 'feature',
@@ -1058,6 +1073,7 @@ export default function MobileEventControlRoomPage() {
icon: Sparkles,
onPress: () => handleModerationAction(featureAction, photo),
disabled: isModerationBusy,
backgroundColor: highlightBg,
},
];
}}

View File

@@ -99,8 +99,16 @@ vi.mock('@tamagui/text', () => ({
}));
vi.mock('@tamagui/react-native-web-lite', () => ({
Pressable: ({ children, onPress }: { children: React.ReactNode; onPress?: () => void }) => (
<button type="button" onClick={onPress}>
Pressable: ({
children,
onPress,
...rest
}: {
children: React.ReactNode;
onPress?: () => void;
[key: string]: unknown;
}) => (
<button type="button" onClick={onPress} {...rest}>
{children}
</button>
),
@@ -114,6 +122,7 @@ vi.mock('../theme', () => ({
border: '#e5e7eb',
accentSoft: '#eef2ff',
accent: '#6366f1',
primary: '#ff5a5f',
danger: '#dc2626',
surfaceMuted: '#f9fafb',
}),
@@ -172,8 +181,8 @@ describe('MobileEventControlRoomPage', () => {
it('renders compact grid actions for moderation photos', async () => {
render(<MobileEventControlRoomPage />);
expect(await screen.findByText('Approve')).toBeInTheDocument();
expect(screen.getByText('Hide')).toBeInTheDocument();
expect(screen.getByText('Set highlight')).toBeInTheDocument();
expect(await screen.findByLabelText('Approve')).toBeInTheDocument();
expect(screen.getByLabelText('Hide')).toBeInTheDocument();
expect(screen.getByLabelText('Set highlight')).toBeInTheDocument();
});
});