190 lines
5.4 KiB
TypeScript
190 lines
5.4 KiB
TypeScript
import React from 'react';
|
|
import { XStack, YStack } from '@tamagui/stacks';
|
|
import { SizableText as Text } from '@tamagui/text';
|
|
import { Button } from '@tamagui/button';
|
|
import { X } from 'lucide-react';
|
|
import { useGuestThemeVariant } from '../lib/guestTheme';
|
|
import { getBentoSurfaceTokens } from '../lib/bento';
|
|
|
|
export type CompassAction = {
|
|
key: string;
|
|
label: string;
|
|
icon?: React.ReactNode;
|
|
onPress?: () => void;
|
|
};
|
|
|
|
type CompassHubProps = {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
quadrants: [CompassAction, CompassAction, CompassAction, CompassAction];
|
|
centerAction: CompassAction;
|
|
title?: string;
|
|
};
|
|
|
|
const quadrantPositions: Array<{
|
|
top?: number;
|
|
right?: number;
|
|
bottom?: number;
|
|
left?: number;
|
|
}> = [
|
|
{ top: 0, left: 0 },
|
|
{ top: 0, right: 0 },
|
|
{ bottom: 0, left: 0 },
|
|
{ bottom: 0, right: 0 },
|
|
];
|
|
|
|
export default function CompassHub({
|
|
open,
|
|
onOpenChange,
|
|
quadrants,
|
|
centerAction,
|
|
title = 'Quick jump',
|
|
}: CompassHubProps) {
|
|
const close = () => onOpenChange(false);
|
|
const { isDark } = useGuestThemeVariant();
|
|
const bentoSurface = getBentoSurfaceTokens(isDark);
|
|
const tileShadow = isDark
|
|
? '0 10px 0 rgba(2, 6, 23, 0.55), 0 20px 24px rgba(2, 6, 23, 0.45)'
|
|
: '0 10px 0 rgba(15, 23, 42, 0.18), 0 18px 22px rgba(15, 23, 42, 0.16)';
|
|
const [visible, setVisible] = React.useState(open);
|
|
const [closing, setClosing] = React.useState(false);
|
|
|
|
React.useEffect(() => {
|
|
if (open) {
|
|
setVisible(true);
|
|
setClosing(false);
|
|
return;
|
|
}
|
|
|
|
if (!visible) return;
|
|
setClosing(true);
|
|
const timeout = window.setTimeout(() => {
|
|
setVisible(false);
|
|
setClosing(false);
|
|
}, 520);
|
|
|
|
return () => {
|
|
window.clearTimeout(timeout);
|
|
};
|
|
}, [open, visible]);
|
|
|
|
if (!visible) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<YStack position="fixed" inset={0} zIndex={100000} pointerEvents="box-none">
|
|
<YStack
|
|
position="absolute"
|
|
inset={0}
|
|
backgroundColor={isDark ? 'rgba(15, 23, 42, 0.55)' : 'rgba(15, 23, 42, 0.22)'}
|
|
pointerEvents="auto"
|
|
onPress={close}
|
|
onClick={close}
|
|
onMouseDown={close}
|
|
onTouchStart={close}
|
|
/>
|
|
<YStack
|
|
position="absolute"
|
|
inset={0}
|
|
padding={24}
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
pointerEvents="box-none"
|
|
>
|
|
<YStack
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
pointerEvents="auto"
|
|
>
|
|
<YStack
|
|
key={closing ? 'compass-out' : 'compass-in'}
|
|
width={280}
|
|
height={280}
|
|
position="relative"
|
|
className={closing ? 'guest-compass-flyout' : 'guest-compass-flyin'}
|
|
>
|
|
<Button
|
|
size="$3"
|
|
circular
|
|
backgroundColor={bentoSurface.backgroundColor}
|
|
borderWidth={1}
|
|
borderBottomWidth={3}
|
|
borderColor={bentoSurface.borderColor}
|
|
borderBottomColor={bentoSurface.borderBottomColor}
|
|
onPress={close}
|
|
aria-label="Close compass"
|
|
style={{
|
|
position: 'absolute',
|
|
right: -18,
|
|
top: -18,
|
|
boxShadow: tileShadow,
|
|
transform: 'rotate(-6deg)',
|
|
zIndex: 2,
|
|
}}
|
|
>
|
|
<X size={16} color={isDark ? '#F8FAFF' : '#0F172A'} />
|
|
</Button>
|
|
{quadrants.map((action, index) => (
|
|
<Button
|
|
key={action.key}
|
|
onPress={() => {
|
|
action.onPress?.();
|
|
close();
|
|
}}
|
|
width={120}
|
|
height={120}
|
|
borderRadius={24}
|
|
backgroundColor={bentoSurface.backgroundColor}
|
|
borderWidth={1}
|
|
borderBottomWidth={3}
|
|
borderColor={bentoSurface.borderColor}
|
|
borderBottomColor={bentoSurface.borderBottomColor}
|
|
position="absolute"
|
|
{...quadrantPositions[index]}
|
|
style={{ boxShadow: tileShadow }}
|
|
>
|
|
<YStack alignItems="center" gap="$2">
|
|
{action.icon}
|
|
<Text fontSize="$3" fontWeight="$7">
|
|
{action.label}
|
|
</Text>
|
|
</YStack>
|
|
</Button>
|
|
))}
|
|
|
|
<Button
|
|
onPress={() => {
|
|
centerAction.onPress?.();
|
|
close();
|
|
}}
|
|
width={90}
|
|
height={90}
|
|
borderRadius={45}
|
|
backgroundColor="$primary"
|
|
borderWidth={1}
|
|
borderBottomWidth={3}
|
|
borderColor={bentoSurface.borderColor}
|
|
borderBottomColor={bentoSurface.borderBottomColor}
|
|
position="absolute"
|
|
top="50%"
|
|
left="50%"
|
|
style={{
|
|
transform: 'translate(-45px, -45px)',
|
|
boxShadow: tileShadow,
|
|
}}
|
|
>
|
|
<YStack alignItems="center" gap="$1">
|
|
{centerAction.icon}
|
|
<Text fontSize="$2" fontWeight="$7" color="white">
|
|
{centerAction.label}
|
|
</Text>
|
|
</YStack>
|
|
</Button>
|
|
</YStack>
|
|
</YStack>
|
|
</YStack>
|
|
</YStack>
|
|
);
|
|
}
|