@@ -5,8 +5,10 @@ import {
55 type ResizeDragEvent ,
66 type ResizeParams ,
77} from "@xyflow/react" ;
8- import { useEffect , useState } from "react" ;
8+ import { type MouseEvent , useEffect , useState } from "react" ;
99
10+ import { Button } from "@/components/ui/button" ;
11+ import { Icon } from "@/components/ui/icon" ;
1012import { BlockStack } from "@/components/ui/layout" ;
1113import { Paragraph } from "@/components/ui/typography" ;
1214import { cn } from "@/lib/utils" ;
@@ -24,7 +26,7 @@ type FlexNodeProps = NodeProps<Node<FlexNodeData>>;
2426const MIN_SIZE = { width : 50 , height : 50 } ;
2527
2628const FlexNode = ( { data, id, selected } : FlexNodeProps ) => {
27- const { properties, readOnly } = data ;
29+ const { properties, readOnly, locked } = data ;
2830 const { title, content, color, borderColor } = properties ;
2931
3032 const [ isInlineEditing , setIsInlineEditing ] = useState ( false ) ;
@@ -42,6 +44,39 @@ const FlexNode = ({ data, id, selected }: FlexNodeProps) => {
4244 setComponentSpec,
4345 } = useComponentSpec ( ) ;
4446
47+ const toggleLock = ( ) => {
48+ const updatedSubgraphSpec = updateFlexNodeInComponentSpec (
49+ currentSubgraphSpec ,
50+ {
51+ ...data ,
52+ locked : ! locked ,
53+ } ,
54+ ) ;
55+
56+ const newRootSpec = updateSubgraphSpec (
57+ componentSpec ,
58+ currentSubgraphPath ,
59+ updatedSubgraphSpec ,
60+ ) ;
61+
62+ setComponentSpec ( newRootSpec ) ;
63+ } ;
64+
65+ const handleLockToggle = ( e : MouseEvent < HTMLButtonElement > ) => {
66+ e . stopPropagation ( ) ;
67+ toggleLock ( ) ;
68+ } ;
69+
70+ const handleDoubleClick = ( ) => {
71+ if ( locked ) {
72+ toggleLock ( ) ;
73+ return ;
74+ }
75+ if ( ! readOnly ) {
76+ setIsInlineEditing ( true ) ;
77+ }
78+ } ;
79+
4580 const handleResizeEnd = ( _ : ResizeDragEvent , params : ResizeParams ) => {
4681 const width = Math . max ( params . width , MIN_SIZE . width ) ;
4782 const height = Math . max ( params . height , MIN_SIZE . height ) ;
@@ -116,7 +151,7 @@ const FlexNode = ({ data, id, selected }: FlexNodeProps) => {
116151 < div
117152 key = { id }
118153 className = { cn (
119- "p-1 rounded-lg h-full w-full" ,
154+ "p-1 rounded-lg h-full w-full group " ,
120155 readOnly && selected && "ring-2 ring-ring" ,
121156 isTransparent && "border-2 border-solid" ,
122157 isTransparent &&
@@ -129,18 +164,33 @@ const FlexNode = ({ data, id, selected }: FlexNodeProps) => {
129164 backgroundColor : color ,
130165 borderColor : isTransparent ? borderColor : undefined ,
131166 } }
132- onDoubleClick = { ( ) => setIsInlineEditing ( true ) }
167+ onDoubleClick = { handleDoubleClick }
133168 >
134169 < div
135170 className = { cn (
136171 "rounded-sm p-1 h-full w-full overflow-hidden" ,
137172 isTransparent ? "bg-transparent" : "bg-white/40" ,
138173 ) }
139174 >
140- < BlockStack gap = "1" >
141- < Paragraph size = "sm" weight = "semibold" >
142- { title }
143- </ Paragraph >
175+ < BlockStack gap = "1" className = "w-full" >
176+ < Button
177+ variant = "ghost"
178+ size = "min"
179+ onClick = { handleLockToggle }
180+ className = { cn (
181+ "absolute top-1 right-1 opacity-50 hover:bg-transparent hover:opacity-100" ,
182+ ! locked && "hidden group-hover:block" ,
183+ ) }
184+ >
185+ < Icon name = { locked ? "Lock" : "LockOpen" } className = "w-2! h-2!" />
186+ </ Button >
187+
188+ { title && (
189+ < Paragraph size = "sm" weight = "semibold" >
190+ { title }
191+ </ Paragraph >
192+ ) }
193+
144194 { isInlineEditing ? (
145195 < InlineTextEditor
146196 value = { content }
0 commit comments