160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import React from 'react';
|
|
import { YStack, XStack } from '@tamagui/stacks';
|
|
import { SizableText as Text } from '@tamagui/text';
|
|
import { Button } from '@tamagui/button';
|
|
import { Pressable } from '@tamagui/react-native-web-lite';
|
|
import { useTheme } from '@tamagui/core';
|
|
import { Home, BarChart2, Settings } from 'lucide-react';
|
|
|
|
export function AppCard({ children, padding = '$4', ...rest }: React.ComponentProps<typeof YStack> & { padding?: keyof typeof rest }) {
|
|
return (
|
|
<YStack
|
|
bg="$surface"
|
|
borderRadius="$card"
|
|
borderWidth={1}
|
|
borderColor="$muted"
|
|
shadowColor="#0f172a"
|
|
shadowOpacity={0.05}
|
|
shadowRadius={12}
|
|
shadowOffset={{ width: 0, height: 8 }}
|
|
padding={padding as any}
|
|
space="$3"
|
|
{...rest}
|
|
>
|
|
{children}
|
|
</YStack>
|
|
);
|
|
}
|
|
|
|
export function StatusPill({ tone = 'muted', children }: { tone?: 'success' | 'warning' | 'muted'; children: React.ReactNode }) {
|
|
const colors: Record<typeof tone, { bg: string; color: string; border: string }> = {
|
|
success: { bg: '#ecfdf3', color: '#047857', border: '#bbf7d0' },
|
|
warning: { bg: '#fffbeb', color: '#92400e', border: '#fef3c7' },
|
|
muted: { bg: '#f3f4f6', color: '#374151', border: '#e5e7eb' },
|
|
};
|
|
const palette = colors[tone] ?? colors.muted;
|
|
return (
|
|
<XStack
|
|
alignItems="center"
|
|
paddingHorizontal="$2.5"
|
|
paddingVertical="$1"
|
|
borderRadius="$pill"
|
|
borderWidth={1}
|
|
backgroundColor={palette.bg}
|
|
borderColor={palette.border}
|
|
alignSelf="flex-start"
|
|
>
|
|
<Text fontSize={11} fontWeight="700" color={palette.color}>
|
|
{children}
|
|
</Text>
|
|
</XStack>
|
|
);
|
|
}
|
|
|
|
export function PrimaryCTA({ label, onPress }: { label: string; onPress: () => void }) {
|
|
return (
|
|
<Button
|
|
backgroundColor="$primary"
|
|
color="white"
|
|
height={56}
|
|
borderRadius="$card"
|
|
fontWeight="700"
|
|
onPress={onPress}
|
|
pressStyle={{ opacity: 0.9 }}
|
|
>
|
|
{label}
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
export function Segmented({
|
|
options,
|
|
value,
|
|
onChange,
|
|
}: {
|
|
options: Array<{ key: string; label: string }>;
|
|
value: string;
|
|
onChange: (key: string) => void;
|
|
}) {
|
|
return (
|
|
<XStack bg="$muted" borderRadius="$pill" borderWidth={1} borderColor="$muted" padding="$1" space="$1">
|
|
{options.map((option) => {
|
|
const active = option.key === value;
|
|
return (
|
|
<Pressable key={option.key} onPress={() => onChange(option.key)} style={{ flex: 1 }}>
|
|
<YStack
|
|
bg={active ? '$primary' : 'transparent'}
|
|
borderRadius="$pill"
|
|
paddingVertical="$2"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
>
|
|
<Text color={active ? 'white' : '$color'} fontWeight="700" fontSize="$sm">
|
|
{option.label}
|
|
</Text>
|
|
</YStack>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</XStack>
|
|
);
|
|
}
|
|
|
|
export function MetaRow({ date, location, status }: { date: string; location: string; status: string }) {
|
|
return (
|
|
<YStack space="$1">
|
|
<Text fontSize="$sm" color="$color">{date}</Text>
|
|
<Text fontSize="$sm" color="$color">{location}</Text>
|
|
<StatusPill tone="muted">{status}</StatusPill>
|
|
</YStack>
|
|
);
|
|
}
|
|
|
|
export function BottomNav({
|
|
active,
|
|
onNavigate,
|
|
}: {
|
|
active: 'events' | 'analytics' | 'settings';
|
|
onNavigate: (key: 'events' | 'analytics' | 'settings') => void;
|
|
}) {
|
|
const theme = useTheme();
|
|
const items = [
|
|
{ key: 'events', icon: Home, label: 'Events' },
|
|
{ key: 'analytics', icon: BarChart2, label: 'Analytics' },
|
|
{ key: 'settings', icon: Settings, label: 'Settings' },
|
|
];
|
|
return (
|
|
<XStack
|
|
position="fixed"
|
|
bottom={0}
|
|
left={0}
|
|
right={0}
|
|
bg="$background"
|
|
borderTopWidth={1}
|
|
borderColor="$muted"
|
|
padding="$3"
|
|
justifyContent="space-around"
|
|
shadowColor="#0f172a"
|
|
shadowOpacity={0.08}
|
|
shadowRadius={12}
|
|
shadowOffset={{ width: 0, height: -4 }}
|
|
zIndex={50}
|
|
>
|
|
{items.map((item) => {
|
|
const activeState = item.key === active;
|
|
const IconCmp = item.icon;
|
|
return (
|
|
<Pressable key={item.key} onPress={() => onNavigate(item.key as typeof active)}>
|
|
<YStack alignItems="center" space="$1">
|
|
<IconCmp size={20} color={activeState ? String(theme.primary?.val ?? '#007AFF') : '#9ca3af'} />
|
|
<Text fontSize="$xs" color={activeState ? '$primary' : '$muted'}>
|
|
{item.label}
|
|
</Text>
|
|
</YStack>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</XStack>
|
|
);
|
|
}
|