Files
app/components/Navigation/MobileBottomBar.tsx
Albert d7f5988a4f refactor: Use Mantine CSS variables and modules for theme styling
Replaced all hardcoded colors and JS template literal styling with Mantine's
canonical approach using CSS modules and CSS variables. This ensures colors
transition programmatically without JS interpolation.

- Updated globals.css to use data-mantine-color-scheme selectors
- Created CSS modules for all navigation components and chat page
- Removed useComputedColorScheme/useMantineTheme hooks where not needed
- Fixed body background to properly adapt to light/dark mode
- All borders, backgrounds, and colors now use CSS variables
- Maintained full theme support across all components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 22:54:15 +00:00

119 lines
3.5 KiB
TypeScript

'use client';
/**
* Mobile Bottom Bar Navigation
*
* Fixed bottom navigation for mobile devices (< 768px).
* Shows three buttons: Convo, Edit, Galaxy
* Highlights the active mode based on app state machine.
*/
import { Group, Button, Paper, ActionIcon, Box, Stack, Text } from '@mantine/core';
import { IconMessageCircle, IconEdit, IconChartBubbleFilled, IconUser } from '@tabler/icons-react';
import { useSelector } from '@xstate/react';
import { useAppMachine } from '@/hooks/useAppMachine';
import { UserMenu } from '@/components/UserMenu';
import { useState, useEffect } from 'react';
import styles from './MobileBottomBar.module.css';
interface UserProfile {
did: string;
handle: string;
displayName: string | null;
avatar: string | null;
}
export function MobileBottomBar() {
const actor = useAppMachine();
const state = useSelector(actor, (state) => state);
const send = actor.send;
const [profile, setProfile] = useState<UserProfile | null>(null);
useEffect(() => {
fetch('/api/user/profile')
.then((res) => res.json())
.then((data) => {
if (!data.error) {
setProfile(data);
}
})
.catch((error) => {
console.error('Failed to fetch profile:', error);
});
}, []);
const handleNavigation = (target: 'convo' | 'edit' | 'galaxy') => {
console.log('[Mobile Nav] Navigating to:', target);
if (target === 'convo') {
send({ type: 'NAVIGATE_TO_CONVO' });
} else if (target === 'edit') {
send({ type: 'NAVIGATE_TO_EDIT' });
} else if (target === 'galaxy') {
send({ type: 'NAVIGATE_TO_GALAXY' });
}
};
const isConvo = state.matches('convo');
const isEdit = state.matches('edit');
const isGalaxy = state.matches('galaxy');
console.log('[Mobile Nav] Current state:', state.value, {
isConvo,
isEdit,
isGalaxy,
});
return (
<Paper
withBorder
p="md"
radius={0}
className={styles.bottomBar}
>
<Group justify="space-around" grow>
<Stack gap={4} align="center" onClick={() => handleNavigation('convo')} style={{ cursor: 'pointer' }}>
<ActionIcon
variant={isConvo ? 'filled' : 'subtle'}
color={isConvo ? 'blue' : 'gray'}
size={40}
radius="md"
>
<IconMessageCircle size={20} />
</ActionIcon>
<Text size="xs" c={isConvo ? 'blue' : 'dimmed'}>Convo</Text>
</Stack>
<Stack gap={4} align="center" onClick={() => handleNavigation('edit')} style={{ cursor: 'pointer' }}>
<ActionIcon
variant={isEdit ? 'filled' : 'subtle'}
color={isEdit ? 'blue' : 'gray'}
size={40}
radius="md"
>
<IconEdit size={20} />
</ActionIcon>
<Text size="xs" c={isEdit ? 'blue' : 'dimmed'}>Manual</Text>
</Stack>
<Stack gap={4} align="center" onClick={() => handleNavigation('galaxy')} style={{ cursor: 'pointer' }}>
<ActionIcon
variant={isGalaxy ? 'filled' : 'subtle'}
color={isGalaxy ? 'blue' : 'gray'}
size={40}
radius="md"
>
<IconChartBubbleFilled size={20} />
</ActionIcon>
<Text size="xs" c={isGalaxy ? 'blue' : 'dimmed'}>Galaxy</Text>
</Stack>
<Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}>
<UserMenu />
<Text size="xs" c="dimmed">Profile</Text>
</Box>
</Group>
</Paper>
);
}