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>
This commit is contained in:
66
app/chat/page.module.css
Normal file
66
app/chat/page.module.css
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/* Message bubbles */
|
||||||
|
.userMessage {
|
||||||
|
align-self: flex-end;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistantMessage {
|
||||||
|
align-self: flex-start;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="light"] .userMessage {
|
||||||
|
background-color: var(--mantine-color-gray-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .userMessage {
|
||||||
|
background-color: var(--mantine-color-dark-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="light"] .assistantMessage {
|
||||||
|
background-color: var(--mantine-color-gray-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .assistantMessage {
|
||||||
|
background-color: var(--mantine-color-dark-7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Voice controls */
|
||||||
|
.voiceControls {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.voiceControls {
|
||||||
|
left: 260px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.voiceControls {
|
||||||
|
bottom: 90px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="light"] .voiceControls {
|
||||||
|
border-top: 1px solid var(--mantine-color-gray-3);
|
||||||
|
background-color: var(--mantine-color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .voiceControls {
|
||||||
|
border-top: 1px solid var(--mantine-color-dark-5);
|
||||||
|
background-color: var(--mantine-color-dark-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dev panel */
|
||||||
|
[data-mantine-color-scheme="light"] .devPanel {
|
||||||
|
background-color: var(--mantine-color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .devPanel {
|
||||||
|
background-color: var(--mantine-color-dark-8);
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import { useAppMachine } from '@/hooks/useAppMachine';
|
|||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { useMediaQuery } from '@mantine/hooks';
|
import { useMediaQuery } from '@mantine/hooks';
|
||||||
import { useSelector } from '@xstate/react';
|
import { useSelector } from '@xstate/react';
|
||||||
|
import styles from './page.module.css';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the voice button text based on the current state
|
* Get the voice button text based on the current state
|
||||||
@@ -242,10 +243,7 @@ export default function ChatPage() {
|
|||||||
shadow="md"
|
shadow="md"
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
style={{
|
className={m.role === 'user' ? styles.userMessage : styles.assistantMessage}
|
||||||
alignSelf: m.role === 'user' ? 'flex-end' : 'flex-start',
|
|
||||||
backgroundColor: m.role === 'user' ? '#343a40' : '#212529',
|
|
||||||
}}
|
|
||||||
w="80%"
|
w="80%"
|
||||||
>
|
>
|
||||||
<Title order={6} size="sm">
|
<Title order={6} size="sm">
|
||||||
@@ -276,7 +274,7 @@ export default function ChatPage() {
|
|||||||
shadow="md"
|
shadow="md"
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
style={{ alignSelf: 'flex-start', backgroundColor: '#212529' }}
|
className={styles.assistantMessage}
|
||||||
w="80%"
|
w="80%"
|
||||||
>
|
>
|
||||||
<Title order={6} size="sm">
|
<Title order={6} size="sm">
|
||||||
@@ -298,7 +296,7 @@ export default function ChatPage() {
|
|||||||
shadow="md"
|
shadow="md"
|
||||||
p="sm"
|
p="sm"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
style={{ alignSelf: 'flex-end', backgroundColor: '#343a40' }}
|
className={styles.userMessage}
|
||||||
w="80%"
|
w="80%"
|
||||||
>
|
>
|
||||||
<Title order={6} size="sm">
|
<Title order={6} size="sm">
|
||||||
@@ -315,15 +313,7 @@ export default function ChatPage() {
|
|||||||
withBorder
|
withBorder
|
||||||
p="md"
|
p="md"
|
||||||
radius={0}
|
radius={0}
|
||||||
style={{
|
className={styles.voiceControls}
|
||||||
position: 'fixed',
|
|
||||||
bottom: isMobile ? '90px' : 0,
|
|
||||||
left: isMobile ? 0 : '260px',
|
|
||||||
right: 0,
|
|
||||||
zIndex: 50,
|
|
||||||
borderTop: '1px solid #373A40',
|
|
||||||
backgroundColor: '#1a1b1e',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Container size="md">
|
<Container size="md">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
@@ -380,7 +370,12 @@ export default function ChatPage() {
|
|||||||
|
|
||||||
{/* Development Test Controls */}
|
{/* Development Test Controls */}
|
||||||
{process.env.NODE_ENV === 'development' && (
|
{process.env.NODE_ENV === 'development' && (
|
||||||
<Paper withBorder p="sm" radius="md" style={{ backgroundColor: '#1a1b1e' }}>
|
<Paper
|
||||||
|
withBorder
|
||||||
|
p="sm"
|
||||||
|
radius="md"
|
||||||
|
className={styles.devPanel}
|
||||||
|
>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Text size="xs" fw={700} c="dimmed">
|
<Text size="xs" fw={700} c="dimmed">
|
||||||
DEV: State Machine Testing
|
DEV: State Machine Testing
|
||||||
|
|||||||
@@ -3,6 +3,15 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #181a1d; /* Our darkest gray */
|
}
|
||||||
color: #e9ecef; /* Our lightest gray */
|
|
||||||
|
/* Use Mantine's data attribute for theme-aware styling */
|
||||||
|
[data-mantine-color-scheme="light"] body {
|
||||||
|
background-color: var(--mantine-color-white);
|
||||||
|
color: var(--mantine-color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] body {
|
||||||
|
background-color: var(--mantine-color-dark-8);
|
||||||
|
color: var(--mantine-color-dark-0);
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/theme.ts
10
app/theme.ts
@@ -56,16 +56,6 @@ export const theme = createTheme({
|
|||||||
radius: 'md',
|
radius: 'md',
|
||||||
withBorder: true,
|
withBorder: true,
|
||||||
},
|
},
|
||||||
styles: (theme) => ({
|
|
||||||
root: {
|
|
||||||
backgroundColor: theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[7]
|
|
||||||
: theme.white,
|
|
||||||
borderColor: theme.colorScheme === 'dark'
|
|
||||||
? theme.colors.dark[5]
|
|
||||||
: theme.colors.gray[3],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
TextInput: {
|
TextInput: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
|
|||||||
27
components/Navigation/DesktopSidebar.module.css
Normal file
27
components/Navigation/DesktopSidebar.module.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.sidebar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="light"] .sidebar {
|
||||||
|
border-right: 1px solid var(--mantine-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .sidebar {
|
||||||
|
border-right: 1px solid var(--mantine-color-dark-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.devPanel {
|
||||||
|
margin-top: var(--mantine-spacing-xl);
|
||||||
|
padding: var(--mantine-spacing-sm);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="light"] .devPanel {
|
||||||
|
border: 1px solid var(--mantine-color-gray-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .devPanel {
|
||||||
|
border: 1px solid var(--mantine-color-dark-4);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { useSelector } from '@xstate/react';
|
|||||||
import { useAppMachine } from '@/hooks/useAppMachine';
|
import { useAppMachine } from '@/hooks/useAppMachine';
|
||||||
import { UserMenu } from '@/components/UserMenu';
|
import { UserMenu } from '@/components/UserMenu';
|
||||||
import { ThemeToggle } from '@/components/ThemeToggle';
|
import { ThemeToggle } from '@/components/ThemeToggle';
|
||||||
|
import styles from './DesktopSidebar.module.css';
|
||||||
|
|
||||||
export function DesktopSidebar() {
|
export function DesktopSidebar() {
|
||||||
const actor = useAppMachine();
|
const actor = useAppMachine();
|
||||||
@@ -43,14 +44,7 @@ export function DesktopSidebar() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box className={styles.sidebar}>
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
borderRight: '1px solid #373A40',
|
|
||||||
padding: '1rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Group gap="md" mb="lg" align="center" wrap="nowrap">
|
<Group gap="md" mb="lg" align="center" wrap="nowrap">
|
||||||
<img
|
<img
|
||||||
@@ -110,7 +104,7 @@ export function DesktopSidebar() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Divider my="md" color="#373A40" />
|
<Divider my="md" />
|
||||||
|
|
||||||
{/* Theme Toggle */}
|
{/* Theme Toggle */}
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
@@ -120,7 +114,7 @@ export function DesktopSidebar() {
|
|||||||
|
|
||||||
{/* Development state panel */}
|
{/* Development state panel */}
|
||||||
{process.env.NODE_ENV === 'development' && (
|
{process.env.NODE_ENV === 'development' && (
|
||||||
<Box mt="xl" p="sm" style={{ border: '1px solid #495057', borderRadius: '4px' }}>
|
<Box className={styles.devPanel}>
|
||||||
<Text size="xs" fw={700} c="dimmed" mb="xs">
|
<Text size="xs" fw={700} c="dimmed" mb="xs">
|
||||||
DEV: App State
|
DEV: App State
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
15
components/Navigation/MobileBottomBar.module.css
Normal file
15
components/Navigation/MobileBottomBar.module.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.bottomBar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="light"] .bottomBar {
|
||||||
|
border-top: 1px solid var(--mantine-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .bottomBar {
|
||||||
|
border-top: 1px solid var(--mantine-color-dark-5);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import { useSelector } from '@xstate/react';
|
|||||||
import { useAppMachine } from '@/hooks/useAppMachine';
|
import { useAppMachine } from '@/hooks/useAppMachine';
|
||||||
import { UserMenu } from '@/components/UserMenu';
|
import { UserMenu } from '@/components/UserMenu';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import styles from './MobileBottomBar.module.css';
|
||||||
|
|
||||||
interface UserProfile {
|
interface UserProfile {
|
||||||
did: string;
|
did: string;
|
||||||
@@ -68,14 +69,7 @@ export function MobileBottomBar() {
|
|||||||
withBorder
|
withBorder
|
||||||
p="md"
|
p="md"
|
||||||
radius={0}
|
radius={0}
|
||||||
style={{
|
className={styles.bottomBar}
|
||||||
position: 'fixed',
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
zIndex: 100,
|
|
||||||
borderTop: '1px solid #373A40',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Group justify="space-around" grow>
|
<Group justify="space-around" grow>
|
||||||
<Stack gap={4} align="center" onClick={() => handleNavigation('convo')} style={{ cursor: 'pointer' }}>
|
<Stack gap={4} align="center" onClick={() => handleNavigation('convo')} style={{ cursor: 'pointer' }}>
|
||||||
|
|||||||
15
components/Navigation/MobileHeader.module.css
Normal file
15
components/Navigation/MobileHeader.module.css
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="light"] .header {
|
||||||
|
border-bottom: 1px solid var(--mantine-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .header {
|
||||||
|
border-bottom: 1px solid var(--mantine-color-dark-5);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Group, Title, Paper } from '@mantine/core';
|
import { Group, Title, Paper } from '@mantine/core';
|
||||||
|
import styles from './MobileHeader.module.css';
|
||||||
|
|
||||||
export function MobileHeader() {
|
export function MobileHeader() {
|
||||||
return (
|
return (
|
||||||
@@ -14,14 +15,7 @@ export function MobileHeader() {
|
|||||||
withBorder
|
withBorder
|
||||||
p="sm"
|
p="sm"
|
||||||
radius={0}
|
radius={0}
|
||||||
style={{
|
className={styles.header}
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
zIndex: 100,
|
|
||||||
borderBottom: '1px solid #373A40',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Group gap="md" align="center" wrap="nowrap">
|
<Group gap="md" align="center" wrap="nowrap">
|
||||||
<img
|
<img
|
||||||
|
|||||||
Reference in New Issue
Block a user