Shrink control room photo actions
This commit is contained in:
@@ -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,
|
||||
},
|
||||
];
|
||||
}}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user