feat: wip excalidraw UX

This commit is contained in:
David Xu
2024-10-22 22:01:23 -07:00
parent 34a32ac474
commit 2c4527f4a1
12 changed files with 483 additions and 53 deletions
+20
View File
@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/styles/tailwind.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
+7 -1
View File
@@ -22,19 +22,25 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/joy": "^5.0.0-beta.48",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-switch": "^1.1.1",
"@tisoap/react-flow-smart-edge": "^3.0.0",
"@vercel/speed-insights": "^1.0.10",
"@xyflow/react": "^12.2.0",
"clsx": "^2.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"file-saver": "^2.0.5",
"is-ci": "^3.0.1",
"jszip": "^3.10.1",
"lucide-react": "^0.453.0",
"next": "^13.4.11",
"openai": "^4.57.0",
"posthog-js": "^1.120.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"reactflow": "^11.11.4",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0"
},
"devDependencies": {
+87 -30
View File
@@ -1,5 +1,6 @@
'use client'
import { useCallback, useState, useEffect, useRef } from 'react'
import { useCallback, useState, useEffect, useRef, act } from 'react'
import Toolbar from './ToolBar'
import {
Background,
ReactFlow,
@@ -14,7 +15,7 @@ import {
import Image from 'next/image'
import { MarkerType } from 'reactflow'
import '@xyflow/react/dist/style.css'
import { ActiveIconProvider } from '@/contexts/ActiveIconContext'
import { initialNodes, nodeTypes, type CustomNodeType } from './nodes'
import { initialEdges, edgeTypes, type CustomEdgeType } from './edges'
import { generateLanggraphCode } from '../codeGeneration/generateLanggraph'
@@ -40,7 +41,42 @@ export default function App() {
const [maxEdgeLength, setMaxEdgeLength] = useState(0)
const { edgeLabels, updateEdgeLabel } = useEdgeLabel()
const [activeIcon, setActiveIcon] = useState<number>(0)
const activeIconRef = useRef(activeIcon)
const [isLocked, setIsLocked] = useState(false)
const isLockedRef = useRef(isLocked)
useEffect(() => {
activeIconRef.current = activeIcon
isLockedRef.current = isLocked
isLockedRef.current = isLocked
}, [activeIcon, isLocked])
const handleNodesChange = useCallback(
(changes: any) => {
if (activeIconRef.current !== 3 && changes[0].type === 'remove') {
console.log('onNodesChange prevented because activeIcon is 1')
return
}
onNodesChange(changes)
},
[onNodesChange],
)
const handleEdgesChange = useCallback(
(changes: any) => {
console.log('changes')
// If the active icon is not 3 (i.e., not in eraser mode), prevent deletion
if (activeIconRef.current !== 3 && changes.some((change: any) => change.type === 'remove')) {
console.log('Edge deletion prevented because activeIcon is not 3')
return
}
onEdgesChange(changes)
},
[onEdgesChange],
)
// onboarding logic
const [modals, setModals] = useState({
showWelcomeModal: false,
showCreateNodeModal: false,
@@ -187,12 +223,16 @@ export default function App() {
},
]
// util functions
const onConnectStart: OnConnectStart = useCallback(() => {
setIsConnecting(true)
}, [nodes, setIsConnecting])
const onConnect: OnConnect = useCallback(
(connection) => {
if (activeIconRef.current === 1 || activeIconRef.current === 0) {
return
}
const edgeId = `edge-${maxEdgeLength + 1}`
setMaxEdgeLength(maxEdgeLength + 1)
const defaultLabel = `default_edge_name`
@@ -226,11 +266,16 @@ export default function App() {
const addNode = useCallback(
(event: React.MouseEvent) => {
event.preventDefault()
if (activeIconRef.current === 1 || activeIconRef.current === 2 || activeIconRef.current === 3) {
return
}
if (isConnecting) {
setIsConnecting(false)
return
}
event.preventDefault()
if (reactFlowWrapper) {
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect()
@@ -292,6 +337,10 @@ export default function App() {
const onEdgeClick = useCallback(
(event: React.MouseEvent, edge: Edge) => {
event.stopPropagation()
if (activeIconRef.current === 1 || activeIconRef.current === 0 || isLockedRef.current) {
return
}
setEdges((eds) => eds.map((e) => (e.id === edge.id ? { ...e, animated: !e.animated } : e)))
},
[setEdges],
@@ -300,6 +349,9 @@ export default function App() {
const onEdgeDoubleClick = useCallback(
(event: React.MouseEvent, edge: Edge) => {
event.stopPropagation()
if (activeIconRef.current !== 3 || isLockedRef.current) {
return
}
setEdges((eds) => eds.filter((e) => e.id !== edge.id))
},
[setEdges],
@@ -307,33 +359,37 @@ export default function App() {
return (
<div ref={reactFlowWrapper} className='z-10 no-scrollbar' style={{ width: '100vw', height: '100vh' }}>
<ReactFlow<CustomNodeType, CustomEdgeType>
onEdgeClick={onEdgeClick}
onEdgeDoubleClick={onEdgeDoubleClick}
nodes={nodes}
nodeTypes={nodeTypes}
onNodesChange={onNodesChange}
edges={edges.map((edge) => {
return {
...edge,
data: { ...edge.data },
}
})}
edgeTypes={edgeTypes}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
fitView
onConnectStart={onConnectStart}
onPaneClick={addNode}
className='z-10 bg-[#1a1c24]'
style={{
backgroundColor: '#1a1c24',
}}
proOptions={proOptions}
>
<Background />
</ReactFlow>
<ActiveIconProvider activeIcon={activeIcon}>
<ReactFlow<CustomNodeType, CustomEdgeType>
onEdgeClick={onEdgeClick}
onEdgeDoubleClick={onEdgeDoubleClick}
nodes={nodes}
nodeTypes={nodeTypes}
onNodesChange={handleNodesChange}
edges={edges.map((edge) => {
return {
...edge,
data: { ...edge.data },
}
})}
edgeTypes={edgeTypes}
onEdgesChange={handleEdgesChange}
onConnect={onConnect}
onInit={setReactFlowInstance}
fitView
onConnectStart={onConnectStart}
onPaneClick={addNode}
className='z-10 bg-[#1a1c24]'
style={{
backgroundColor: '#1a1c24',
}}
proOptions={proOptions}
connectionLineStyle={{ opacity: activeIcon === 1 || activeIcon === 0 ? 0 : 1 }}
>
<Background />
</ReactFlow>
</ActiveIconProvider>
<div className='flex rounded py-2 px-4 flex-col absolute bottom-4 right-4'>
<div className='text-white font-bold text-center'> {'Generate Code'}</div>
<div className='flex flex-row gap-2 pt-3'>
@@ -345,6 +401,7 @@ export default function App() {
</Button>
</div>
</div>
<Toolbar setActiveIcon={setActiveIcon} activeIcon={activeIcon} setIsLocked={setIsLocked} isLocked={isLocked} />
{genericModalArray.map((modal, index) => {
return <GenericModal key={index} {...modal} />
+59
View File
@@ -0,0 +1,59 @@
import React, { useState } from 'react'
import { Switch } from '@/components/ui/switch'
import Image from 'next/image'
import { Button } from '@mui/joy'
import { SquareMousePointer, PenLine, Hand, Eraser } from 'lucide-react'
interface ToolbarProps {
setActiveIcon: (id: number) => void
activeIcon: number
isLocked: boolean
setIsLocked: (value: boolean) => void
}
const Toolbar = ({ setActiveIcon, activeIcon, isLocked, setIsLocked }: ToolbarProps) => {
const icons = [
{ id: 0, name: 'Node', component: <SquareMousePointer size={20} /> },
{ id: 1, name: 'Position', component: <Hand size={20} /> },
{ id: 2, name: 'Edge', component: <PenLine size={20} /> },
{ id: 3, name: 'Eraser', component: <Eraser size={20} /> },
{ id: 4, name: 'Type', component: <Image src='/python.png' alt='Type' width={20} height={20} /> },
{ id: 5, name: 'Type', component: <Image src='/javascript.png' alt='Type' width={20} height={20} /> },
]
return (
<div className='flex flex-row gap-x-3 justify-center items-center p-2 fixed left-1/2 transform -translate-x-1/2 top-10 z-50'>
<div className='flex flex-row bg-slate-900 rounded-lg shadow-md'>
{icons.map((icon) => (
<div key={icon.id} className='relative'>
<Button
key={icon.id}
className={`m-1 p-2 ${
activeIcon === icon.id ? 'bg-slate-900' : 'bg-slate-800'
} rounded hover:bg-slate-900 focus:outline-none`}
onClick={() => setActiveIcon(icon.id)}
>
{icon.component}
</Button>
</div>
))}
</div>
{activeIcon === 2 && (
<div
className={`flex flex-row justify-center items-center gap-x-3 transition-opacity duration-500 ease-out opacity-100 transform-gpu translate-y-0 ${
activeIcon === 2 ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2'
}`}
>
<Switch
checked={isLocked}
onCheckedChange={(checked) => setIsLocked(checked)}
className='data-[state=checked]:bg-green-500 data-[state=unchecked]:bg-slate-100'
/>
<div className='text-md text-slate-100'>Lock edges</div>
</div>
)}
</div>
)
}
export default Toolbar
+13 -2
View File
@@ -1,7 +1,8 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react'
import { useEdgeLabel } from '@/contexts/EdgeLabelContext'
import { useButtonText } from '@/contexts/ButtonTextContext'
import { useActiveIcon } from '@/contexts/ActiveIconContext'
interface SelfConnectingEdgeProps extends EdgeProps {
data?: {
@@ -12,14 +13,22 @@ interface SelfConnectingEdgeProps extends EdgeProps {
export default function SelfConnectingEdge(props: SelfConnectingEdgeProps) {
const { sourceX, sourceY, targetX, targetY, id, markerEnd, source, label, animated } = props
const { activeIcon } = useActiveIcon()
const { edgeLabels, updateEdgeLabel } = useEdgeLabel()
const { buttonTexts } = useButtonText()
const [isEditing, setIsEditing] = useState(false)
const [currentLabel, setCurrentLabel] = useState(edgeLabels[source] || 'default_edge_name')
useEffect(() => {
// Sync currentLabel with the context whenever the edgeLabels or source changes
setCurrentLabel(edgeLabels[source] || 'default_edge_name')
}, [edgeLabels, source])
const handleLabelClick = (e: React.MouseEvent) => {
e.stopPropagation()
if (activeIcon === 1 || activeIcon === 0) {
return
}
setIsEditing(true)
}
@@ -78,6 +87,7 @@ export default function SelfConnectingEdge(props: SelfConnectingEdgeProps) {
(isEditing ? (
<foreignObject className='pointer-events-none' x={labelX - 70} y={labelY - 10} width={130} height={35}>
<input
disabled={activeIcon === 1 || activeIcon === 0}
data-stop-propagation='true'
type='text'
value={currentLabel}
@@ -119,6 +129,7 @@ export default function SelfConnectingEdge(props: SelfConnectingEdgeProps) {
(isEditing ? (
<foreignObject x={sourceX + 30} y={sourceY + 5} width={130} height={35}>
<input
disabled={activeIcon === 1 || activeIcon === 0}
type='text'
value={currentLabel}
onChange={handleInputChange}
+27 -14
View File
@@ -2,6 +2,7 @@ import { Handle, Position } from '@xyflow/react'
import type { Node as NodeType, NodeProps } from '@xyflow/react'
import { useCallback, useState, useMemo, useRef, useEffect } from 'react'
import { useButtonText } from '@/contexts/ButtonTextContext'
import { useActiveIcon } from '@/contexts/ActiveIconContext'
export type CustomNodeData = {
label: string
@@ -11,7 +12,8 @@ export type CustomNode = NodeType<CustomNodeData>
export default function CustomNode({ data, id }: NodeProps<CustomNode>) {
const { buttonTexts, updateButtonText } = useButtonText()
const [nodeWidth, setNodeWidth] = useState(150) // Default width
const { activeIcon } = useActiveIcon()
const [nodeWidth, setNodeWidth] = useState(150)
const inputRef = useRef<HTMLInputElement>(null)
const randomBorderColor = useMemo(() => {
@@ -29,7 +31,7 @@ export default function CustomNode({ data, id }: NodeProps<CustomNode>) {
const adjustNodeSize = useCallback(() => {
if (inputRef.current) {
const textWidth = inputRef.current.scrollWidth
const newWidth = Math.max(150, textWidth) // 20px for padding
const newWidth = Math.max(150, textWidth)
setNodeWidth(newWidth)
}
}, [])
@@ -52,18 +54,29 @@ export default function CustomNode({ data, id }: NodeProps<CustomNode>) {
width: `${nodeWidth}px`,
}}
>
<input
ref={inputRef}
type='text'
className='w-full outline-none rounded-md text-center p-0 text-white'
value={buttonTexts[id]}
onChange={handleInputChange}
style={{
backgroundColor: 'transparent',
color: randomBorderColor,
width: '100%',
}}
/>
{activeIcon !== 1 && activeIcon !== 2 ? (
<input
ref={inputRef}
type='text'
className='w-full outline-none rounded-md text-center p-0 text-white'
value={buttonTexts[id]}
onChange={handleInputChange}
style={{
backgroundColor: 'transparent',
color: randomBorderColor,
width: '100%',
}}
/>
) : (
<div
className='w-full outline-none rounded-md text-center p-0'
style={{
color: randomBorderColor, // Set the color of the text to match the border color
}}
>
{buttonTexts[id]}
</div>
)}
<Handle type='source' style={{ width: '10px', height: '10px' }} position={Position.Bottom} />
<Handle type='target' style={{ width: '10px', height: '10px' }} position={Position.Top} />
+27
View File
@@ -0,0 +1,27 @@
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }
+24
View File
@@ -0,0 +1,24 @@
import React, { createContext, useContext, ReactNode } from 'react'
type ActiveIconContextType = {
activeIcon: number
}
type ActiveIconProviderProps = {
activeIcon: number
children: ReactNode
}
const ActiveIconContext = createContext<ActiveIconContextType | undefined>(undefined)
export const ActiveIconProvider: React.FC<ActiveIconProviderProps> = ({ activeIcon, children }) => {
return <ActiveIconContext.Provider value={{ activeIcon }}>{children}</ActiveIconContext.Provider>
}
export const useActiveIcon = (): ActiveIconContextType => {
const context = useContext(ActiveIconContext)
if (!context) {
throw new Error('useActiveIcon must be used within an ActiveIconProvider')
}
return context
}
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+65
View File
@@ -53,4 +53,69 @@ path.react-flow__edge-path {
path.react-flow__edge-path:hover {
stroke-width: 5;
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
+46 -2
View File
@@ -1,8 +1,8 @@
/* eslint-disable import/no-extraneous-dependencies */
const defaultTheme = require('tailwindcss/defaultTheme')
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class'],
content: ['./pages/**/*.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
@@ -19,12 +19,56 @@ module.exports = {
800: '#1e40af',
900: '#1e3a8a',
950: '#172554',
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
1: 'hsl(var(--chart-1))',
2: 'hsl(var(--chart-2))',
3: 'hsl(var(--chart-3))',
4: 'hsl(var(--chart-4))',
5: 'hsl(var(--chart-5))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
},
primary: 'var(--primary)',
black: 'var(--black)',
white: 'var(--white)',
},
plugins: [],
/* eslint-disable-next-line global-require */
plugins: [require('tailwindcss-animate')],
}
+102 -4
View File
@@ -2089,6 +2089,82 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@radix-ui/primitive@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2"
integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==
"@radix-ui/react-compose-refs@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74"
integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==
"@radix-ui/react-context@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a"
integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==
"@radix-ui/react-icons@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.3.0.tgz#c61af8f323d87682c5ca76b856d60c2312dbcb69"
integrity sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==
"@radix-ui/react-primitive@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884"
integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==
dependencies:
"@radix-ui/react-slot" "1.1.0"
"@radix-ui/react-slot@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84"
integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==
dependencies:
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-switch@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.1.1.tgz#1401658c24d66a18610f18793afbaa7fedf5429a"
integrity sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==
dependencies:
"@radix-ui/primitive" "1.1.0"
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-context" "1.1.1"
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-controllable-state" "1.1.0"
"@radix-ui/react-use-previous" "1.1.0"
"@radix-ui/react-use-size" "1.1.0"
"@radix-ui/react-use-callback-ref@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==
"@radix-ui/react-use-controllable-state@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0"
integrity sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==
dependencies:
"@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-layout-effect@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
"@radix-ui/react-use-previous@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c"
integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==
"@radix-ui/react-use-size@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz#b4dba7fbd3882ee09e8d2a44a3eed3a7e555246b"
integrity sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==
dependencies:
"@radix-ui/react-use-layout-effect" "1.1.0"
"@reactflow/background@11.3.14":
version "11.3.14"
resolved "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz"
@@ -3553,6 +3629,13 @@ cjs-module-lexer@^1.0.0:
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz"
integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==
class-variance-authority@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522"
integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==
dependencies:
clsx "2.0.0"
classcat@^5.0.3, classcat@^5.0.4:
version "5.0.5"
resolved "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz"
@@ -3609,10 +3692,10 @@ clone@^1.0.2:
resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
clsx@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz"
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
clsx@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
clsx@^2.1.0, clsx@^2.1.1:
version "2.1.1"
@@ -6725,6 +6808,11 @@ lru-cache@^6.0.0:
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz"
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
lucide-react@^0.453.0:
version "0.453.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.453.0.tgz#d37909a45a29d89680383a202ee861224b05ba6a"
integrity sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz"
@@ -8563,6 +8651,16 @@ synckit@^0.8.6:
"@pkgr/core" "^0.1.0"
tslib "^2.6.2"
tailwind-merge@^2.5.4:
version "2.5.4"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.5.4.tgz#4bf574e81fa061adeceba099ae4df56edcee78d1"
integrity sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==
tailwindcss-animate@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz#318b692c4c42676cc9e67b19b78775742388bef4"
integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==
tailwindcss@^3.3.3:
version "3.4.1"
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz"