mirror of
https://github.com/langchain-ai/langgraph-builder.git
synced 2026-07-01 19:55:58 -04:00
feat: ✨ wip excalidraw UX
This commit is contained in:
@@ -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
@@ -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
@@ -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} />
|
||||
|
||||
@@ -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
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -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
@@ -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')],
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user