first implementation of tamagui mobile pages
This commit is contained in:
159
resources/js/admin/tamagui/primitives.tsx
Normal file
159
resources/js/admin/tamagui/primitives.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user