Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/src/components/IngredientReadView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ export default function IngredientReadView({
<Box>
{/* Header Section */}
<Box mb={spacing['2xl']}>
<Flex justify="space-between" align="flex-start" mb={spacing.lg}>
<Flex
justify="space-between"
align="flex-start"
mb={spacing.lg}
direction={{ base: 'column', sm: 'row' }}
gap={spacing.md}
>
<Box>
<Title
order={1}
Expand Down
10 changes: 9 additions & 1 deletion app/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useRef } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { AppShell } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { spacing } from '@/designTokens';
import { cacheMonitor } from '@/utils/cacheMonitor';
import GiveCalcBanner from './shared/GiveCalcBanner';
Expand All @@ -10,6 +11,7 @@ import Sidebar from './Sidebar';
export default function Layout() {
const location = useLocation();
const previousLocation = useRef(location.pathname);
const [navbarOpened, { toggle: toggleNavbar, close: closeNavbar }] = useDisclosure();

// Track navigation for cache monitoring
useEffect(() => {
Expand All @@ -22,6 +24,11 @@ export default function Layout() {
}
}, [location.pathname]);

// Close navbar on route change (mobile UX)
useEffect(() => {
closeNavbar();
}, [location.pathname, closeNavbar]);

// Otherwise, render the normal layout with AppShell
return (
<AppShell
Expand All @@ -30,10 +37,11 @@ export default function Layout() {
navbar={{
width: parseInt(spacing.appShell.navbar.width, 10),
breakpoint: spacing.appShell.navbar.breakpoint,
collapsed: { mobile: !navbarOpened },
}}
>
<AppShell.Header p={0}>
<HeaderNavigation />
<HeaderNavigation navbarOpened={navbarOpened} onToggleNavbar={toggleNavbar} />
<GiveCalcBanner />
</AppShell.Header>

Expand Down
7 changes: 2 additions & 5 deletions app/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
import { Box, Button, Stack } from '@mantine/core';
import { WEBSITE_URL } from '@/constants';
import { useCurrentCountry } from '@/hooks/useCurrentCountry';
import { colors, spacing, typography } from '../designTokens';
import { colors, typography } from '../designTokens';
import SidebarDivider from './sidebar/SidebarDivider';
import SidebarNavItem from './sidebar/SidebarNavItem';
import SidebarSection from './sidebar/SidebarSection';
Expand Down Expand Up @@ -88,13 +88,10 @@ export default function Sidebar({ isOpen = true }: SidebarProps) {

return (
<Stack
h="100vh"
h="100%"
bg="white"
style={{
borderRight: `1px solid ${colors.border.light}`,
width: parseInt(spacing.appShell.navbar.width, 10),
left: 0,
top: 0,
overflowY: 'auto',
}}
gap={0}
Expand Down
13 changes: 12 additions & 1 deletion app/src/components/StandardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
* children are rendered directly without double-wrapping.
*/

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { AppShell } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { LayoutProvider, useIsInsideLayout } from '@/contexts/LayoutContext';
import { spacing } from '@/designTokens';
import GiveCalcBanner from './shared/GiveCalcBanner';
Expand All @@ -21,6 +24,13 @@ interface StandardLayoutProps {

export default function StandardLayout({ children }: StandardLayoutProps) {
const isInsideLayout = useIsInsideLayout();
const location = useLocation();
const [navbarOpened, { toggle: toggleNavbar, close: closeNavbar }] = useDisclosure();

// Close navbar on route change (mobile UX)
useEffect(() => {
closeNavbar();
}, [location.pathname, closeNavbar]);

// If already inside a StandardLayout, just render children directly
// This prevents double-wrapping when pathways are inside router-provided layouts
Expand All @@ -36,10 +46,11 @@ export default function StandardLayout({ children }: StandardLayoutProps) {
navbar={{
width: parseInt(spacing.appShell.navbar.width, 10),
breakpoint: spacing.appShell.navbar.breakpoint,
collapsed: { mobile: !navbarOpened },
}}
>
<AppShell.Header p={0}>
<HeaderNavigation />
<HeaderNavigation navbarOpened={navbarOpened} onToggleNavbar={toggleNavbar} />
<GiveCalcBanner />
</AppShell.Header>

Expand Down
2 changes: 1 addition & 1 deletion app/src/components/charts/ImpactTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function ImpactTooltip({ active, payload }: ImpactTooltipProps) {
}
const data = payload[0].payload;
return (
<div style={{ ...TOOLTIP_STYLE, maxWidth: 300 }}>
<div style={{ ...TOOLTIP_STYLE, maxWidth: 'min(300px, 90vw)' }}>
<p style={{ fontWeight: 600, margin: 0 }}>{data.name}</p>
<p style={{ margin: '4px 0 0', fontSize: 13, whiteSpace: 'pre-wrap' }}>{data.hoverText}</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/charts/WaterfallChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function WaterfallTooltip({ active, payload }: any) {
}
const data = payload[0].payload as WaterfallDatum;
return (
<div style={{ ...TOOLTIP_STYLE, maxWidth: 300 }}>
<div style={{ ...TOOLTIP_STYLE, maxWidth: 'min(300px, 90vw)' }}>
<p style={{ fontWeight: 600, margin: 0 }}>{data.name}</p>
</div>
);
Expand Down
40 changes: 18 additions & 22 deletions app/src/components/common/DataTable.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,31 @@
import { Table } from '@mantine/core';
import { Box, Table } from '@mantine/core';

interface DataTableProps<T> {
data: T[];
columns: { key: keyof T; header: string }[];
}

export default function DataTable<T>({ data, columns }: DataTableProps<T>) {
// const [scrolled, setScrolled] = useState(false);
return (
// <ScrollArea h={300} onScrollPositionChange={({ y }) => setScrolled(y !== 0)}>
<Table miw={700}>
<Table.Thead>
<Table.Tr>
{columns.map((col) => (
<th key={String(col.key)}>{col.header}</th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{data.map((row, i) => (
// <tr key={i}>
<Table.Tr key={i} ta="center">
<Box style={{ overflowX: 'auto', WebkitOverflowScrolling: 'touch' }}>
<Table miw={700}>
<Table.Thead>
<Table.Tr>
{columns.map((col) => (
<td key={String(col.key)}>{String(row[col.key])}</td>
<th key={String(col.key)}>{col.header}</th>
))}
</Table.Tr>

// </tr>
))}
</Table.Tbody>
</Table>
// </ScrollArea>
</Table.Thead>
<Table.Tbody>
{data.map((row, i) => (
<Table.Tr key={i} ta="center">
{columns.map((col) => (
<td key={String(col.key)}>{String(row[col.key])}</td>
))}
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Box>
);
}
8 changes: 5 additions & 3 deletions app/src/components/common/MultiButtonFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ export default function MultiButtonFooter(props: MultiButtonFooterProps) {
// New layout: Grid with equal spacing - Cancel left, Pagination center, Back/Next right
if (cancelAction || backAction || primaryAction) {
return (
<SimpleGrid cols={3} spacing="md">
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="md">
{/* Left side: Cancel button */}
<Box style={{ display: 'flex', justifyContent: 'flex-start' }}>
{cancelAction && (
<Button variant="outline" onClick={cancelAction.onClick}>
<Button variant="outline" onClick={cancelAction.onClick} fullWidth>
{cancelAction.label}
</Button>
)}
Expand All @@ -55,12 +55,13 @@ export default function MultiButtonFooter(props: MultiButtonFooterProps) {

{/* Right side: Back and Primary buttons */}
<Box style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Group gap="sm" wrap="nowrap">
<Group gap="sm" wrap="nowrap" style={{ width: '100%' }}>
{backAction && (
<Button
variant="outline"
onClick={backAction.onClick}
leftSection={<IconChevronLeft size={16} />}
fullWidth
>
{backAction.label}
</Button>
Expand All @@ -72,6 +73,7 @@ export default function MultiButtonFooter(props: MultiButtonFooterProps) {
loading={primaryAction.isLoading}
disabled={primaryAction.isDisabled}
rightSection={<IconChevronRight size={16} />}
fullWidth
>
{primaryAction.label}
</Button>
Expand Down
1 change: 1 addition & 0 deletions app/src/components/home/MainSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function MainSection() {
style={{
lineHeight: typography.lineHeight.tight,
fontFamily: typography.fontFamily.primary,
fontSize: 'clamp(28px, 5vw, 48px)',
}}
>
Start simulating
Expand Down
26 changes: 24 additions & 2 deletions app/src/components/homeHeader/HeaderContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Container, Group } from '@mantine/core';
import { IconDotsVertical } from '@tabler/icons-react';
import { ActionIcon, Container, Group } from '@mantine/core';
import { colors } from '@/designTokens';
import DesktopNavigation from './DesktopNavigation';
import HeaderActionButtons from './HeaderActionButtons';
import HeaderLogo from './HeaderLogo';
Expand All @@ -10,9 +12,18 @@ interface HeaderContentProps {
onOpen: () => void;
onClose: () => void;
navItems: NavItemSetup[];
navbarOpened?: boolean;
onToggleNavbar?: () => void;
}

export default function HeaderContent({ opened, onOpen, onClose, navItems }: HeaderContentProps) {
export default function HeaderContent({
opened,
onOpen,
onClose,
navItems,
navbarOpened: _navbarOpened,
onToggleNavbar,
}: HeaderContentProps) {
return (
<Container
h="100%"
Expand All @@ -27,6 +38,17 @@ export default function HeaderContent({ opened, onOpen, onClose, navItems }: Hea
>
<Group justify="space-between" h="100%">
<Group>
{onToggleNavbar && (
<ActionIcon
variant="subtle"
color={colors.text.inverse}
onClick={onToggleNavbar}
hiddenFrom="sm"
aria-label="Toggle sidebar"
>
<IconDotsVertical size={20} />
</ActionIcon>
)}
<HeaderLogo />
<DesktopNavigation navItems={navItems} />
</Group>
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/household/HouseholdBuilderForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export default function HouseholdBuilderForm({
position: 'fixed',
top: `calc(${spacing.appShell.header.height} + ${spacing.xl})`,
right: spacing.xl,
maxWidth: 400,
maxWidth: 'min(400px, calc(100vw - 40px))',
zIndex: 1000,
opacity: 1,
backgroundColor: colors.white,
Expand Down
16 changes: 14 additions & 2 deletions app/src/components/shared/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { NavItemSetup } from '@/components/homeHeader/NavItem';
import { colors, spacing, typography } from '@/designTokens';
import { useWebsitePath } from '@/hooks/useWebsitePath';

export default function HeaderNavigation() {
interface HeaderNavigationProps {
navbarOpened?: boolean;
onToggleNavbar?: () => void;
}

export default function HeaderNavigation({ navbarOpened, onToggleNavbar }: HeaderNavigationProps) {
const [opened, { open, close }] = useDisclosure(false);
const { getWebsitePath } = useWebsitePath();

Expand Down Expand Up @@ -61,7 +66,14 @@ export default function HeaderNavigation() {
borderRadius: spacing.radius.none,
}}
>
<HeaderContent opened={opened} onOpen={open} onClose={close} navItems={navItems} />
<HeaderContent
opened={opened}
onOpen={open}
onClose={close}
navItems={navItems}
navbarOpened={navbarOpened}
onToggleNavbar={onToggleNavbar}
/>
</div>
);
}
13 changes: 5 additions & 8 deletions app/src/components/shared/static/SupporterCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,19 @@ export default function SupporterCard({ supporter, projects }: SupporterCardProp
<Box
style={{
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
gap: spacing.lg,
marginBottom: 24,
}}
>
{supporter.logoUrl && (
<a
href={supporter.websiteUrl}
target="_blank"
rel="noopener noreferrer"
style={{ marginRight: 24 }}
>
<a href={supporter.websiteUrl} target="_blank" rel="noopener noreferrer">
<Image
src={supporter.logoUrl}
alt={`${supporter.name} logo`}
w={200}
h={80}
w={{ base: 150, sm: 200 }}
h={{ base: 60, sm: 80 }}
fit="contain"
style={{
objectPosition: 'left center',
Expand Down
8 changes: 4 additions & 4 deletions app/src/components/shared/static/TeamMemberCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ export default function TeamMemberCard({ member, variant = 'default' }: TeamMemb
gutter="5vw"
align="stretch"
>
<Grid.Col span="content">
<Grid.Col span={{ base: 12, sm: 'content' }}>
<Image
src={member.image}
alt={member.name}
h={250}
w={250}
h={{ base: 180, sm: 250 }}
w={{ base: 180, sm: 250 }}
radius={spacing.radius.container}
style={{
objectFit: 'cover',
}}
/>
</Grid.Col>

<Grid.Col span="auto">
<Grid.Col span={{ base: 12, sm: 'auto' }}>
<Box
style={{
borderBottom: `1px solid ${borderColor}`,
Expand Down
3 changes: 3 additions & 0 deletions app/src/hooks/useChartDimensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import { useEffect, useState } from 'react';
import { useMantineTheme } from '@mantine/core';

/** Media query string for mobile breakpoint — matches Mantine 'sm' (48em / 768px) */
export const MOBILE_BREAKPOINT_QUERY = '(max-width: 48em)';

/**
* Hook to track chart container width using ResizeObserver
* @param containerRef - Reference to the container element
Expand Down
Loading