feat: show labels on zoom

This commit is contained in:
dextmorgn
2025-02-10 23:03:03 +01:00
parent eb7fea5b70
commit b5290ac3c0
17 changed files with 126 additions and 74 deletions

View File

@@ -21,13 +21,13 @@ const DashboardLayout = async ({
<Flex className='h-screen w-screen flex'>
<Flex className='h-screen'>
<div className='flex flex-col w-[300px] h-full rounded-none shadow-none border-r border-gray-400/20'>
<div className='w-full rounded-none shadow-none h-14 border-b border-gray-400/20 flex items-center gap-1 flex-row justify-between p-2'>
<div className='w-full rounded-none shadow-none h-12 border-b border-gray-400/20 flex items-center gap-1 flex-row justify-between p-2'>
<Logo />
<Flex gap={"1"}>
<NewCase />
</Flex>
</div>
<ScrollArea type="auto" scrollbars="vertical" className='p-3 w-full !h-[calc(100vh_-56px)] grow overflow-y-auto'>
<ScrollArea type="auto" scrollbars="vertical" className='p-3 w-full !h-[calc(100vh_-48px)] grow overflow-y-auto'>
<div className="flex flex-col">
</div>
@@ -37,8 +37,8 @@ const DashboardLayout = async ({
</Flex>
<Flex className='grow flex flex-col'>
<div>
<div className='w-full rounded-none shadow-none !h-14 justify-between border-b border-gray-400/20 flex flex-row justify-between items-center p-2'>
<TextField.Root placeholder="Search an investigation..." className='w-full max-w-xs !h-10'>
<div className='w-full rounded-none shadow-none !h-12 justify-between border-b border-gray-400/20 flex flex-row justify-between items-center p-2'>
<TextField.Root placeholder="Search an investigation..." className='w-full max-w-xs !h-8'>
<TextField.Slot>
<MagnifyingGlassIcon height="16" width="16" />
</TextField.Slot>

View File

@@ -1,9 +1,7 @@
import { InvestigationProvider } from '@/src/components/contexts/investigation-provider';
import InvestigationLayout from '@/src/components/investigations/layout';
import { getInvestigation } from '@/src/lib/actions/investigations';
import Individuals from './individuals';
import { SearchProvider } from '@/src/components/contexts/search-context';
import { notFound } from 'next/navigation';
import { createClient } from "@/src/lib/supabase/server";
import { redirect } from "next/navigation";
import { ChatProvider } from '@/src/components/contexts/chatbot-context';
@@ -21,14 +19,12 @@ const DashboardLayout = async ({
redirect('/login')
}
const { investigation_id } = await (params)
const { error } = await getInvestigation(investigation_id)
if (error) return notFound()
return (
<InvestigationProvider>
<SearchProvider>
<ChatProvider>
<InvestigationLayout investigation_id={investigation_id} left={<Individuals investigation_id={investigation_id} />}>
<InvestigationLayout user={data.user} investigation_id={investigation_id} left={<Individuals investigation_id={investigation_id} />}>
{children}
</InvestigationLayout>
</ChatProvider>

View File

@@ -0,0 +1,10 @@
import { Spinner } from '@radix-ui/themes'
import React from 'react'
const loading = () => {
return (
<div className='h-[90vh] w-full flex items-center justify-center gap-2'><Spinner /> Loading nodes and edges...</div>
)
}
export default loading

View File

@@ -0,0 +1,10 @@
import { Spinner } from '@radix-ui/themes'
import React from 'react'
const loading = () => {
return (
<div className='h-screen w-screen flex items-center justify-center gap-2'><Spinner /> Loading investigation...</div>
)
}
export default loading

View File

@@ -1,7 +1,7 @@
'use client';
import { createContext, ReactNode, useContext, useState } from 'react';
import { Avatar, Box, Card, Dialog, Flex, Spinner, Text, TextField } from '@radix-ui/themes';
import { Avatar, Box, Card, Dialog, Flex, Spinner, Text, TextArea, TextField } from '@radix-ui/themes';
import { useChat } from '@ai-sdk/react';
import { BotIcon } from 'lucide-react';
import { cn } from '@/src/lib/utils';
@@ -100,7 +100,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
<Text size="2" as='div' weight="bold" className={cn(m.role === 'user' ? 'text-right' : 'text-left')}>
{m.role === 'user' ? 'User' : 'Chatbot'}
</Text>
<Card className='max-w-[70%]'>
<Card className='max-w-[80%]'>
<Box>
<Text as="div" size="2" color="gray">
<ReactMarkdown>{m.content}</ReactMarkdown>
@@ -135,11 +135,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
</Flex>}
</Flex>
<form onSubmit={handleSubmit} className="mt-4">
<TextField.Root
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
<Box width={"100%"}>
<TextField.Root value={input}
onChange={handleInputChange}
size="2" placeholder="'What pattern can you extract from those relations ?'" />
</Box>
</form>
</Dialog.Content>
</Dialog.Root>

View File

@@ -20,7 +20,9 @@ interface InvestigationContextType {
handleOpenIndividualModal: any,
handleDeleteInvestigation: any,
currentNode: any,
setCurrentNode: any
setCurrentNode: any,
panelOpen: boolean,
setPanelOpen: any
}
const InvestigationContext = createContext<InvestigationContextType | undefined>(undefined);
@@ -33,6 +35,7 @@ export const InvestigationProvider: React.FC<InvestigationProviderProps> = ({ ch
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const [panelOpen, setPanelOpen] = useLocalStorage('panel_open', false)
const [filters, setFilters] = useLocalStorage('filters', {});
const [currentNode, setCurrentNode] = useState<null | Node>(null)
const { investigation, isLoading: isLoadingInvestigation } = useInvestigation(investigation_id)
@@ -40,7 +43,9 @@ export const InvestigationProvider: React.FC<InvestigationProviderProps> = ({ ch
const { confirm } = useConfirm()
const [settings, setSettings] = useLocalStorage('settings', {
showNodeLabel: true,
showEdgeLabel: true
showEdgeLabel: true,
showMiniMap: true,
showCopyIcon: true
});
const createQueryString = useCallback(
@@ -76,7 +81,7 @@ export const InvestigationProvider: React.FC<InvestigationProviderProps> = ({ ch
</div>
)
return (
<InvestigationContext.Provider value={{ filters, setFilters, settings, setSettings, setOpenSettingsModal, investigation, isLoadingInvestigation, handleOpenIndividualModal, handleDeleteInvestigation, currentNode, setCurrentNode }}>
<InvestigationContext.Provider value={{ filters, setFilters, settings, setSettings, setOpenSettingsModal, investigation, isLoadingInvestigation, handleOpenIndividualModal, handleDeleteInvestigation, currentNode, setCurrentNode, panelOpen, setPanelOpen }}>
{children}
<Dialog.Root open={openSettingsModal}>
<Dialog.Content maxWidth="450px">
@@ -87,6 +92,8 @@ export const InvestigationProvider: React.FC<InvestigationProviderProps> = ({ ch
<Flex direction="column" gap="3">
<SettingSwitch setting={"showNodeLabel"} value={settings.showNodeLabel} title={"Show labels on nodes"} description={"Displays the labels on the nodes, like username or avatar."} />
<SettingSwitch setting={"showEdgeLabel"} value={settings.showEdgeLabel} title={"Show labels on edges"} description={"Displays the labels on the edges, like relation type."} />
<SettingSwitch setting={"showMiniMap"} value={settings.showMiniMap} title={"Show minimap on the canva"} description={"Displays the minimap on canva."} />
<SettingSwitch setting={"showCopyIcon"} value={settings.showCopyIcon} title={"Show copy button on nodes"} description={"Displays a copy button on the nodes."} />
<div className="flex items-center justify-between gap-4">
<div className="flex flex-col gap-1">
<p className="font-medium">Theme</p>

View File

@@ -32,7 +32,7 @@ export function CopyButton({ content, delay = 2000 }: CopyButtonProps) {
return (
<Tooltip open={isCopied} content="Copied !">
<IconButton size={"1"} color="gray" radius="large" variant="ghost" onClick={handleCopy} aria-label="Copy content">
<Copy className="h-3 w-3" />
<Copy className="h-3 w-3 opacity-50" />
</IconButton>
</Tooltip>
)

View File

@@ -29,7 +29,7 @@ import AddressNode from './nodes/physical_address'
import { AlignCenterHorizontal, AlignCenterVertical, LockOpenIcon, MaximizeIcon, RotateCcwIcon, ZoomInIcon, ZoomOutIcon, LockIcon } from 'lucide-react';
import { useTheme } from 'next-themes';
import NewActions from './new-actions';
import { IconButton, Tooltip, Spinner, Card, Flex } from '@radix-ui/themes';
import { IconButton, Tooltip, Spinner, Card, Flex, SegmentedControl } from '@radix-ui/themes';
import { isNode, isEdge, getIncomers, getOutgoers } from "@xyflow/react";
import { EdgeBase } from '@xyflow/system';
import { useInvestigationContext } from '../contexts/investigation-provider';
@@ -72,7 +72,7 @@ const LayoutFlow = ({ initialNodes, initialEdges, theme }: { initialNodes: any,
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const [isLocked, setIsLocked] = useState(false)
const { currentNode, setCurrentNode } = useInvestigationContext()
const { currentNode, setCurrentNode, settings } = useInvestigationContext()
const ref = useRef(null);
const getAllIncomers = useCallback((node: any, nodes: any[], edges: EdgeBase[], prevIncomers = []) => {
const incomers = getIncomers(node, nodes, edges);
@@ -293,6 +293,13 @@ const LayoutFlow = ({ initialNodes, initialEdges, theme }: { initialNodes: any,
</IconButton>
</Tooltip>
</Panel>
<Panel position="top-center" className='flex items-center gap-1'>
<SegmentedControl.Root defaultValue="graph">
<SegmentedControl.Item value="graph">Graph</SegmentedControl.Item>
<SegmentedControl.Item value="timeline">Timeline</SegmentedControl.Item>
<SegmentedControl.Item value="map">Map</SegmentedControl.Item>
</SegmentedControl.Root>
</Panel>
<Panel position="top-right" className='flex items-center gap-1'>
<Flex direction={"column"} align={"end"} gap={"1"}>
<Flex gap="1">
@@ -337,9 +344,9 @@ const LayoutFlow = ({ initialNodes, initialEdges, theme }: { initialNodes: any,
</Tooltip>
</Panel>
<Background />
<MiniMap pannable />
{settings.showMiniMap && <MiniMap pannable />}
</ReactFlow>
</div>
</div >
);
};

View File

@@ -1,30 +1,33 @@
"use client"
import { Investigation } from '@/src/types/investigation';
import React from 'react'
import { ThemeSwitch } from '../theme-switch';
import MoreMenu from './more-menu';
import CaseSelector from './case-selector';
import NewCase from '../dashboard/new-case';
import SearchModal from '../dashboard/seach-palette';
import { Flex, IconButton, ScrollArea } from '@radix-ui/themes';
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { BotMessageSquareIcon } from 'lucide-react';
import { BotMessageSquareIcon, PanelRightIcon } from 'lucide-react';
import { useChatContext } from '../contexts/chatbot-context';
import Logo from '../logo';
import { useInvestigationContext } from '../contexts/investigation-provider';
import User from '../user';
const InvestigationLayout = ({
children,
left,
investigation_id
investigation_id,
user
}: {
children: React.ReactNode;
left: React.ReactNode;
investigation_id: string
user: any
}) => {
const { setOpen: setOpenChat } = useChatContext()
const { panelOpen, setPanelOpen } = useInvestigationContext()
return (
<PanelGroup className='h-screen w-screen flex' direction="horizontal">
<Panel className='h-screen' defaultSize={20} minSize={20}>
<PanelGroup autoSaveId="conditional" className='h-screen w-screen flex' direction="horizontal">
{panelOpen && <Panel id="left" className='h-screen' defaultSize={20} minSize={10}>
<div className='flex flex-col w-full h-full rounded-none shadow-none border-r border-gray-400/20'>
<div className='w-full rounded-none shadow-none h-12 border-b border-gray-400/20 flex items-center gap-1 flex-row justify-between p-2'>
<Logo />
@@ -39,14 +42,21 @@ const InvestigationLayout = ({
{left}
</div>
</ScrollArea>
<Flex justify={"end"} className='p-2'>
<User user={user} />
</Flex>
</div>
</Panel>
</Panel>}
<PanelResizeHandle />
<Panel defaultSize={80} minSize={50} className='grow flex flex-col'>
<Panel id="center" defaultSize={80} minSize={50} className='grow flex flex-col'>
<div>
<div className='w-full rounded-none shadow-none h-12 justify-between border-b border-gray-400/20 flex flex-row items-center p-2'>
<CaseSelector />
<Flex gap={"1"}>
<IconButton onClick={() => setPanelOpen(!panelOpen)} variant='soft' color='gray'>
<PanelRightIcon className='h-4 w-4' />
</IconButton>
<CaseSelector />
</Flex>
<MoreMenu />
</div>
{children}

View File

@@ -1,25 +1,26 @@
"use client"
import React, { memo } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Handle, Position, useStore } from '@xyflow/react';
import { Card, Box, Text, ContextMenu, Badge, Flex, Inset, Tooltip, Avatar } from '@radix-ui/themes';
import { NodeProvider, useNodeContext } from '../../contexts/node-context';
import { AtSignIcon, ZapIcon } from 'lucide-react';
import { cn } from '@/src/lib/utils';
import { cn, zoomSelector } from '@/src/lib/utils';
import { useInvestigationContext } from '../../contexts/investigation-provider';
import { CopyButton } from '../../copy';
import { useSearchContext } from '../../contexts/search-context';
function EmailNode({ data }: any) {
const { handleDeleteNode, loading } = useNodeContext()
const { settings } = useInvestigationContext()
const { settings, currentNode } = useInvestigationContext()
const { handleOpenSearchModal } = useSearchContext()
const showContent = useStore(zoomSelector);
return (
<ContextMenu.Root>
<ContextMenu.Trigger>
<Box className={cn(loading ? "!opacity-40" : "!opacity-100")}>
{settings.showNodeLabel ?
<Card>
{settings.showNodeLabel && showContent ?
<Card className={cn('border border-transparent hover:border-sky-400', currentNode === data.id && "border-sky-400")}>
<Inset>
<Flex className='items-center p-0'>
<Badge color='green' className='!h-[24px] !rounded-r-none'>
@@ -29,7 +30,7 @@ function EmailNode({ data }: any) {
<Text as="div" size="1" weight="regular">
{data.label}
</Text>
<CopyButton content={data.label} />
{settings.showCopyIcon && <CopyButton content={data.label} />}
</Flex>
</Flex>
</Inset>
@@ -63,7 +64,7 @@ function EmailNode({ data }: any) {
Delete
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
</ContextMenu.Root >
);
}

View File

@@ -1,26 +1,28 @@
"use client"
import React, { memo } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Handle, Position, useStore } from '@xyflow/react';
import { useInvestigationContext } from '../../contexts/investigation-provider';
import { Avatar, Card, Box, Flex, Text, ContextMenu, Spinner, Badge, Tooltip } from '@radix-ui/themes';
import { AtSignIcon, CameraIcon, FacebookIcon, InstagramIcon, LocateIcon, MessageCircleDashedIcon, PhoneIcon, SendIcon, UserIcon, MapPinHouseIcon, ZapIcon, BotIcon } from 'lucide-react';
import { NodeProvider, useNodeContext } from '../../contexts/node-context';
import { useSearchContext } from '../../contexts/search-context';
import { cn } from '@/src/lib/utils';
import { cn, zoomSelector } from '@/src/lib/utils';
import { CopyButton } from '../../copy';
import { useChatContext } from '../../contexts/chatbot-context';
function Custom(props: any) {
const { settings, handleOpenIndividualModal, currentNode } = useInvestigationContext()
const { setOpenAddNodeModal, handleDuplicateNode, handleDeleteNode, loading } = useNodeContext()
const { handleOpenSearchModal } = useSearchContext()
const { setOpen: setOpenChat } = useChatContext()
const showContent = useStore(zoomSelector);
const { data } = props
return (
<>
<ContextMenu.Root>
<ContextMenu.Trigger onContextMenu={(e) => { e.stopPropagation() }}>
<Box className={cn(loading ? "!opacity-40" : "!opacity-100")}>{settings.showNodeLabel ?
<Box className={cn(loading ? "!opacity-40" : "!opacity-100")}>{settings.showNodeLabel && showContent ?
<Card onDoubleClick={() => handleOpenIndividualModal(data.id)} className={cn('!py-1 border border-transparent hover:border-sky-400', currentNode === data.id && "border-sky-400")}>
<Flex gap="2" align="center">
<Avatar
@@ -30,12 +32,12 @@ function Custom(props: any) {
radius="full"
fallback={loading ? <Spinner /> : data.full_name[0]}
/>
{settings.showNodeLabel &&
{settings.showNodeLabel && showContent &&
<Flex align={"center"} gap="2">
<Text as="div" size="1" weight="regular">
{data.full_name}
</Text>
<CopyButton content={data.full_name} />
{settings.showCopyIcon && <CopyButton content={data.full_name} />}
</Flex>}
</Flex>
</Card> :

View File

@@ -1,25 +1,27 @@
"use client"
import React, { memo } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Handle, Position, useStore } from '@xyflow/react';
import { Card, Box, Text, ContextMenu, Flex, Inset, Badge, Tooltip, Avatar } from '@radix-ui/themes';
import { NodeProvider, useNodeContext } from '../../contexts/node-context';
import { LocateIcon, ZapIcon } from 'lucide-react';
import { cn } from '@/src/lib/utils';
import { cn, zoomSelector } from '@/src/lib/utils';
import { useInvestigationContext } from '../../contexts/investigation-provider';
import { CopyButton } from '../../copy';
import { useSearchContext } from '../../contexts/search-context';
function Custom({ data }: any) {
const { handleDeleteNode, loading } = useNodeContext()
const { settings } = useInvestigationContext()
const { settings, currentNode } = useInvestigationContext()
const { handleOpenSearchModal } = useSearchContext()
const showContent = useStore(zoomSelector);
return (
<>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Box className={cn(loading ? "!opacity-40" : "!opacity-100")}>
{settings.showNodeLabel ?
<Card>
{settings.showNodeLabel && showContent ?
<Card className={cn('border border-transparent hover:border-sky-400', currentNode === data.id && "border-sky-400")}>
<Inset>
<Flex className='items-center p-0'>
<Badge color='orange' className='!h-[24px] !rounded-r-none'>
@@ -29,7 +31,7 @@ function Custom({ data }: any) {
<Text as="div" size="1" weight="regular">
{data.label}
</Text>
<CopyButton content={data.label} />
{settings.showCopyIcon && <CopyButton content={data.label} />}
</Flex>
</Flex>
</Inset>

View File

@@ -1,25 +1,27 @@
"use client"
import React, { memo } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Handle, Position, useStore } from '@xyflow/react';
import { Card, Box, Text, ContextMenu, Flex, Inset, Badge, Tooltip, Avatar } from '@radix-ui/themes';
import { NodeProvider, useNodeContext } from '../../contexts/node-context';
import { PhoneIcon, ZapIcon } from 'lucide-react';
import { cn } from '@/src/lib/utils';
import { cn, zoomSelector } from '@/src/lib/utils';
import { useInvestigationContext } from '../../contexts/investigation-provider';
import { CopyButton } from '../../copy';
import { useSearchContext } from '../../contexts/search-context';
function Custom({ data }: any) {
const { handleDeleteNode, loading } = useNodeContext()
const { settings } = useInvestigationContext()
const { settings, currentNode } = useInvestigationContext()
const { handleOpenSearchModal } = useSearchContext()
const showContent = useStore(zoomSelector);
return (
<>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Box className={cn(loading ? "!opacity-40" : "!opacity-100")}>
{settings.showNodeLabel ?
<Card>
{settings.showNodeLabel && showContent ?
<Card className={cn('border border-transparent hover:border-sky-400', currentNode === data.id && "border-sky-400")}>
<Inset>
<Flex className='items-center p-0'>
<Badge color='violet' className='!h-[24px] !rounded-r-none'>
@@ -29,7 +31,7 @@ function Custom({ data }: any) {
<Text as="div" size="1" weight="regular">
{data.label}
</Text>
<CopyButton content={data.label} />
{settings.showCopyIcon && <CopyButton content={data.label} />}
</Flex>
</Flex>
</Inset>

View File

@@ -1,24 +1,26 @@
"use client"
import React, { memo } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Handle, Position, useStore } from '@xyflow/react';
import { Card, Box, Text, ContextMenu, Badge, Flex, Inset, Tooltip, Avatar } from '@radix-ui/themes';
import { NodeProvider, useNodeContext } from '../../contexts/node-context';
import { MapPinHouseIcon, ZapIcon } from 'lucide-react';
import { cn } from '@/src/lib/utils';
import { cn, zoomSelector } from '@/src/lib/utils';
import { useInvestigationContext } from '../../contexts/investigation-provider';
import { CopyButton } from '../../copy';
import { useSearchContext } from '../../contexts/search-context';
function AddressNode({ data }: any) {
const { handleDeleteNode, loading } = useNodeContext()
const { settings } = useInvestigationContext()
const { settings, currentNode } = useInvestigationContext()
const { handleOpenSearchModal } = useSearchContext()
const showContent = useStore(zoomSelector);
return (
<ContextMenu.Root>
<ContextMenu.Trigger>
<Box className={cn(loading ? "!opacity-40" : "!opacity-100")}>
{settings.showNodeLabel ?
<Card>
{settings.showNodeLabel && showContent ?
<Card className={cn('border border-transparent hover:border-sky-400', currentNode === data.id && "border-sky-400")}>
<Inset>
<Flex className='items-center p-0'>
<Badge color='ruby' className='!h-[24px] !rounded-r-none'>
@@ -28,7 +30,7 @@ function AddressNode({ data }: any) {
<Text as="div" size="1" weight="regular">
{data.label}
</Text>
<CopyButton content={data.label} />
{settings.showCopyIcon && <CopyButton content={data.label} />}
</Flex>
</Flex>
</Inset>

View File

@@ -1,9 +1,9 @@
"use client"
import React, { memo } from 'react';
import { Handle, Position } from '@xyflow/react';
import { Handle, Position, useStore } from '@xyflow/react';
import { Card, Box, Text, ContextMenu, Flex, Inset, Badge, Tooltip, Avatar } from '@radix-ui/themes';
import { NodeProvider, useNodeContext } from '../../contexts/node-context';
import { cn } from '@/src/lib/utils';
import { cn, zoomSelector } from '@/src/lib/utils';
import { useInvestigationContext } from '../../contexts/investigation-provider';
import { usePlatformIcons } from '@/src/lib/hooks/use-platform-icons';
import { CopyButton } from '../../copy';
@@ -12,16 +12,18 @@ import { useSearchContext } from '../../contexts/search-context';
function Custom({ data }: any) {
const { handleDeleteNode, loading } = useNodeContext()
const { settings } = useInvestigationContext()
const { settings, currentNode } = useInvestigationContext()
const platformsIcons = usePlatformIcons()
const { handleOpenSearchModal } = useSearchContext()
const showContent = useStore(zoomSelector);
return (
<>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Box className={cn(loading ? "!opacity-40" : "!opacity-100")}>
{settings.showNodeLabel ?
<Card>
{settings.showNodeLabel && showContent ?
<Card className={cn('border border-transparent hover:border-sky-400', currentNode === data.id && "border-sky-400")}>
<Inset>
<Flex className='items-center p-0'>
{/* @ts-ignore */}
@@ -35,8 +37,7 @@ function Custom({ data }: any) {
{data.username || data.profile_url}
</Text>
<CopyButton content={data.username || data.profile_url}
/>
{settings.showCopyIcon && <CopyButton content={data.username || data.profile_url} />}
</Flex>
</Flex>
</Inset>

View File

@@ -1,15 +1,15 @@
import { Avatar, Button, DropdownMenu, Inset, Text } from '@radix-ui/themes'
import { Avatar, Button, DropdownMenu, IconButton, Inset, Text } from '@radix-ui/themes'
import React from 'react'
const User = ({ user }: any) => {
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button className='!mr-1' size={"1"} variant='ghost'>
<IconButton radius='full' className='!mr-1' size={"1"} variant='ghost'>
<Inset>
<Avatar size={"2"} radius='full' src={user?.user_metadata?.avatar_url} fallback={user?.user_metadata?.user_name?.[0] || "?"} variant="soft" />
</Inset>
</Button>
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content className='p-2'>
<Text weight={"medium"}>{user?.user_metadata?.user_name}</Text>

View File

@@ -4,3 +4,5 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export const zoomSelector = (s: { transform: number[]; }) => s.transform[2] >= 0.6;