Skip to content

Commit 7ad4119

Browse files
feat(sticky-note): enhance sticky note functionality with color picker and resizer
1 parent 0549692 commit 7ad4119

2 files changed

Lines changed: 252 additions & 75 deletions

File tree

packages/ui/src/views/agentflowsv2/StickyNote.jsx

Lines changed: 155 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
import PropTypes from 'prop-types'
2-
import { useRef, useContext, useState } from 'react'
2+
import { useRef, useContext, useState, useEffect } from 'react'
33
import { useSelector } from 'react-redux'
4-
import { NodeToolbar } from 'reactflow'
4+
import { NodeToolbar, NodeResizer } from 'reactflow'
5+
import { IconCopy, IconTrash, IconPalette } from '@tabler/icons-react'
6+
import { SketchPicker } from 'react-color'
57

68
// material-ui
79
import { styled, useTheme, alpha, darken, lighten } from '@mui/material/styles'
10+
import { ButtonGroup, IconButton, Box, Popover } from '@mui/material'
811

912
// project imports
10-
import { ButtonGroup, IconButton, Box } from '@mui/material'
11-
import { IconCopy, IconTrash } from '@tabler/icons-react'
1213
import { Input } from '@/ui-component/input/Input'
1314
import MainCard from '@/ui-component/cards/MainCard'
1415

1516
// const
1617
import { flowContext } from '@/store/context/ReactFlowContext'
1718

19+
const MIN_WIDTH = 240
20+
const MIN_HEIGHT = 65
21+
1822
const CardWrapper = styled(MainCard)(({ theme }) => ({
1923
background: theme.palette.card.main,
2024
color: theme.darkTextPrimary,
@@ -41,10 +45,22 @@ const StickyNote = ({ data }) => {
4145
const { reactFlowInstance, deleteNode, duplicateNode } = useContext(flowContext)
4246
const [inputParam] = data.inputParams
4347
const [isHovered, setIsHovered] = useState(false)
48+
const [openToolbar, setOpenToolbar] = useState(false)
49+
const [colorPickerAnchor, setColorPickerAnchor] = useState(null)
50+
const [width, setWidth] = useState(data.inputs.size?.width || MIN_WIDTH)
51+
const [height, setHeight] = useState(data.inputs.size?.height || MIN_HEIGHT)
4452

4553
const defaultColor = '#666666' // fallback color if data.color is not present
4654
const nodeColor = data.color || defaultColor
4755

56+
useEffect(() => {
57+
if (data.selected) {
58+
setOpenToolbar(true)
59+
} else {
60+
setOpenToolbar(false)
61+
}
62+
}, [data.selected])
63+
4864
// Get different shades of the color based on state
4965
const getStateColor = () => {
5066
if (data.selected) return nodeColor
@@ -59,73 +75,148 @@ const StickyNote = ({ data }) => {
5975
return isHovered ? lighten(nodeColor, 0.8) : lighten(nodeColor, 0.9)
6076
}
6177

78+
const handleSizeChange = (newWidth, newHeight) => {
79+
setWidth(newWidth)
80+
setHeight(newHeight)
81+
data.inputs.size = { width: newWidth, height: newHeight }
82+
}
83+
84+
const handleColorPickerOpen = (event) => {
85+
setColorPickerAnchor(event.currentTarget)
86+
}
87+
88+
const handleColorPickerClose = () => {
89+
setColorPickerAnchor(null)
90+
}
91+
92+
const handleColorChange = (color) => {
93+
data.color = color.hex
94+
}
95+
96+
const openColorPicker = Boolean(colorPickerAnchor)
97+
6298
return (
63-
<div ref={ref} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
64-
<StyledNodeToolbar>
65-
<ButtonGroup sx={{ gap: 1 }} variant='outlined' aria-label='Basic button group'>
66-
<IconButton
67-
size={'small'}
68-
title='Duplicate'
69-
onClick={() => {
70-
duplicateNode(data.id)
71-
}}
72-
sx={{
73-
color: customization.isDarkMode ? 'white' : 'inherit',
74-
'&:hover': {
75-
color: theme.palette.primary.main
76-
}
77-
}}
78-
>
79-
<IconCopy size={20} />
80-
</IconButton>
81-
<IconButton
82-
size={'small'}
83-
title='Delete'
84-
onClick={() => {
85-
deleteNode(data.id)
86-
}}
99+
<>
100+
<NodeResizer
101+
color='transparent'
102+
isVisible={true}
103+
minWidth={MIN_WIDTH}
104+
minHeight={MIN_HEIGHT}
105+
onResize={(_event, direction) => {
106+
handleSizeChange(direction.width, direction.height)
107+
}}
108+
onResizeEnd={(_event, direction) => {
109+
handleSizeChange(direction.width, direction.height)
110+
}}
111+
/>
112+
<div ref={ref} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
113+
<StyledNodeToolbar isVisible={openToolbar || openColorPicker}>
114+
<ButtonGroup sx={{ gap: 1 }} variant='outlined' aria-label='Basic button group'>
115+
<IconButton
116+
size={'small'}
117+
title='Change Color'
118+
onClick={handleColorPickerOpen}
119+
sx={{
120+
color: customization.isDarkMode ? 'white' : 'inherit',
121+
'&:hover': {
122+
color: theme.palette.primary.main
123+
}
124+
}}
125+
>
126+
<IconPalette size={20} />
127+
</IconButton>
128+
<IconButton
129+
size={'small'}
130+
title='Duplicate'
131+
onClick={() => {
132+
duplicateNode(data.id)
133+
}}
134+
sx={{
135+
color: customization.isDarkMode ? 'white' : 'inherit',
136+
'&:hover': {
137+
color: theme.palette.primary.main
138+
}
139+
}}
140+
>
141+
<IconCopy size={20} />
142+
</IconButton>
143+
<IconButton
144+
size={'small'}
145+
title='Delete'
146+
onClick={() => {
147+
deleteNode(data.id)
148+
}}
149+
sx={{
150+
color: customization.isDarkMode ? 'white' : 'inherit',
151+
'&:hover': {
152+
color: theme.palette.error.main
153+
}
154+
}}
155+
>
156+
<IconTrash size={20} />
157+
</IconButton>
158+
</ButtonGroup>
159+
</StyledNodeToolbar>
160+
<CardWrapper
161+
content={false}
162+
sx={{
163+
borderColor: getStateColor(),
164+
borderWidth: '1px',
165+
boxShadow: data.selected ? `0 0 0 1px ${getStateColor()} !important` : 'none',
166+
width: width,
167+
height: height,
168+
backgroundColor: getBackgroundColor(),
169+
overflow: 'hidden',
170+
'&:hover': {
171+
boxShadow: data.selected ? `0 0 0 1px ${getStateColor()} !important` : 'none'
172+
}
173+
}}
174+
border={false}
175+
>
176+
<Box
87177
sx={{
88-
color: customization.isDarkMode ? 'white' : 'inherit',
89-
'&:hover': {
90-
color: theme.palette.error.main
91-
}
178+
width: '100%',
179+
height: '100%',
180+
overflowX: 'hidden',
181+
overflowY: 'auto'
92182
}}
93183
>
94-
<IconTrash size={20} />
95-
</IconButton>
96-
</ButtonGroup>
97-
</StyledNodeToolbar>
98-
<CardWrapper
99-
content={false}
100-
sx={{
101-
borderColor: getStateColor(),
102-
borderWidth: '1px',
103-
boxShadow: data.selected ? `0 0 0 1px ${getStateColor()} !important` : 'none',
104-
minHeight: 60,
105-
height: 'auto',
106-
backgroundColor: getBackgroundColor(),
107-
display: 'flex',
108-
alignItems: 'center',
109-
'&:hover': {
110-
boxShadow: data.selected ? `0 0 0 1px ${getStateColor()} !important` : 'none'
184+
<Input
185+
key={data.id}
186+
placeholder={inputParam.placeholder}
187+
inputParam={inputParam}
188+
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
189+
value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
190+
nodes={reactFlowInstance ? reactFlowInstance.getNodes() : []}
191+
edges={reactFlowInstance ? reactFlowInstance.getEdges() : []}
192+
nodeId={data.id}
193+
/>
194+
</Box>
195+
</CardWrapper>
196+
</div>
197+
<Popover
198+
open={openColorPicker}
199+
anchorEl={colorPickerAnchor}
200+
onClose={handleColorPickerClose}
201+
anchorOrigin={{
202+
vertical: 'top',
203+
horizontal: 'right'
204+
}}
205+
transformOrigin={{
206+
vertical: 'top',
207+
horizontal: 'left'
208+
}}
209+
slotProps={{
210+
paper: {
211+
sx: {
212+
marginLeft: '16px'
213+
}
111214
}
112215
}}
113-
border={false}
114216
>
115-
<Box>
116-
<Input
117-
key={data.id}
118-
placeholder={inputParam.placeholder}
119-
inputParam={inputParam}
120-
onChange={(newValue) => (data.inputs[inputParam.name] = newValue)}
121-
value={data.inputs[inputParam.name] ?? inputParam.default ?? ''}
122-
nodes={reactFlowInstance ? reactFlowInstance.getNodes() : []}
123-
edges={reactFlowInstance ? reactFlowInstance.getEdges() : []}
124-
nodeId={data.id}
125-
/>
126-
</Box>
127-
</CardWrapper>
128-
</div>
217+
<SketchPicker color={nodeColor} onChange={handleColorChange} />
218+
</Popover>
219+
</>
129220
)
130221
}
131222

0 commit comments

Comments
 (0)