@inspirer-dev/crm-dashboard 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/admin/src/components/ButtonsBuilder/index.tsx +226 -120
  2. package/admin/src/components/StepFlowBuilder/constants.ts +65 -18
  3. package/admin/src/components/StepFlowBuilder/edges/LabeledEdge.tsx +25 -18
  4. package/admin/src/components/StepFlowBuilder/flow-canvas/FlowCanvas.tsx +1 -0
  5. package/admin/src/components/StepFlowBuilder/hooks/useFlowTheme.ts +50 -0
  6. package/admin/src/components/StepFlowBuilder/nodes/BranchNode.tsx +75 -43
  7. package/admin/src/components/StepFlowBuilder/nodes/EntryNode.tsx +61 -20
  8. package/admin/src/components/StepFlowBuilder/nodes/ExitNode.tsx +55 -20
  9. package/admin/src/components/StepFlowBuilder/nodes/MessageNode.tsx +92 -39
  10. package/admin/src/components/StepFlowBuilder/nodes/WaitNode.tsx +72 -32
  11. package/admin/src/components/StepFlowBuilder/panels/BranchConfig.tsx +151 -35
  12. package/admin/src/components/StepFlowBuilder/panels/EntryConfig.tsx +308 -102
  13. package/admin/src/components/StepFlowBuilder/panels/MessageConfig.tsx +215 -89
  14. package/admin/src/components/StepFlowBuilder/panels/NodeEditPanel.tsx +139 -36
  15. package/admin/src/components/StepFlowBuilder/panels/WaitConfig.tsx +74 -35
  16. package/admin/src/components/StepFlowBuilder/toolbar/FlowToolbar.tsx +124 -28
  17. package/admin/src/components/TriggerConfigField/index.tsx +599 -196
  18. package/admin/src/pages/HomePage/components/FlowBuilderTest.tsx +151 -0
  19. package/admin/src/pages/HomePage/index.tsx +11 -1
  20. package/dist/_chunks/FlowCanvas-C83ErNYt.js +2658 -0
  21. package/dist/_chunks/FlowCanvas-DeOEnPV0.mjs +2634 -0
  22. package/dist/_chunks/index-Bj5go8Fq.mjs +642 -0
  23. package/dist/_chunks/index-BnNkOgKl.js +296 -0
  24. package/dist/_chunks/{index-XoiSAQhK.js → index-Bxicsk1k.js} +461 -237
  25. package/dist/_chunks/index-C7_DxUFr.mjs +296 -0
  26. package/dist/_chunks/index-CY3rfPtB.js +642 -0
  27. package/dist/_chunks/index-CcWOJefE.mjs +92 -0
  28. package/dist/_chunks/{index-CWnuAWMG.mjs → index-DlRQd6UH.mjs} +350 -147
  29. package/dist/_chunks/index-LDAX1BUG.js +92 -0
  30. package/dist/admin/index.js +4 -4
  31. package/dist/admin/index.mjs +4 -4
  32. package/package.json +1 -1
  33. package/dist/_chunks/index-BdNpSOmd.js +0 -277
  34. package/dist/_chunks/index-CQJKdWYb.mjs +0 -181
  35. package/dist/_chunks/index-CVBrqZnU.js +0 -181
  36. package/dist/_chunks/index-CuPgk6KX.mjs +0 -1617
  37. package/dist/_chunks/index-DXHJL-v5.mjs +0 -277
  38. package/dist/_chunks/index-PR1uC6PJ.js +0 -1620
@@ -1,6 +1,5 @@
1
1
  import React, { forwardRef, useEffect, useMemo, useState } from 'react';
2
2
  import {
3
- Badge,
4
3
  Box,
5
4
  Button,
6
5
  Field,
@@ -9,8 +8,11 @@ import {
9
8
  TextInput,
10
9
  Typography,
11
10
  Tooltip,
11
+ Card,
12
+ CardContent,
13
+ useTheme,
12
14
  } from '@strapi/design-system';
13
- import { Plus, Trash, ArrowUp, ArrowDown } from '@strapi/icons';
15
+ import { Plus, Trash, ArrowUp, ArrowDown, Link as LinkIcon } from '@strapi/icons';
14
16
  import { generateId } from '../RulesBuilder/utils';
15
17
 
16
18
  type TelegramButton = {
@@ -73,9 +75,64 @@ const isValidUrl = (url: string): boolean => {
73
75
  }
74
76
  };
75
77
 
78
+ const useThemeColors = () => {
79
+ const theme = useTheme();
80
+ const isDark = theme.colors.neutral0 === '#212134';
81
+
82
+ return useMemo(() => ({
83
+ isDark,
84
+ emptyState: {
85
+ border: isDark ? '#32324d' : '#dcdce4',
86
+ },
87
+ card: {
88
+ border: isDark ? '#32324d' : '#eaeaef',
89
+ borderError: isDark ? '#ee5e52' : '#ee5e52',
90
+ },
91
+ indexBadge: {
92
+ background: isDark ? '#2d2d4a' : '#f0f0ff',
93
+ },
94
+ preview: {
95
+ background: isDark
96
+ ? 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)'
97
+ : 'linear-gradient(135deg, #1e3a5f 0%, #0e2439 100%)',
98
+ },
99
+ }), [isDark]);
100
+ };
101
+
102
+ const TelegramButtonPreview: React.FC<{ text: string; url: string }> = ({ text, url }) => (
103
+ <a
104
+ href={url || '#'}
105
+ target="_blank"
106
+ rel="noopener noreferrer"
107
+ onClick={(e) => !url && e.preventDefault()}
108
+ style={{
109
+ display: 'inline-flex',
110
+ alignItems: 'center',
111
+ justifyContent: 'center',
112
+ gap: '6px',
113
+ padding: '8px 16px',
114
+ backgroundColor: '#3390ec',
115
+ color: '#ffffff',
116
+ borderRadius: '8px',
117
+ textDecoration: 'none',
118
+ fontSize: '14px',
119
+ fontWeight: 500,
120
+ cursor: url ? 'pointer' : 'default',
121
+ transition: 'background-color 0.15s ease',
122
+ minWidth: '80px',
123
+ }}
124
+ onMouseEnter={(e) => url && (e.currentTarget.style.backgroundColor = '#2b7ed8')}
125
+ onMouseLeave={(e) => (e.currentTarget.style.backgroundColor = '#3390ec')}
126
+ >
127
+ <LinkIcon width={14} height={14} style={{ opacity: 0.9 }} />
128
+ <span>{text || '(empty)'}</span>
129
+ </a>
130
+ );
131
+
76
132
  const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
77
133
  ({ name, value, onChange, intlLabel, disabled, error, required, hint }, ref) => {
78
134
  const [buttons, setButtons] = useState<TelegramButton[]>(() => parseButtons(value));
135
+ const colors = useThemeColors();
79
136
 
80
137
  useEffect(() => {
81
138
  setButtons(parseButtons(value));
@@ -122,133 +179,183 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
122
179
 
123
180
  return (
124
181
  <Field.Root name={name} error={error} required={required} hint={hint} ref={ref}>
125
- <Flex direction="column" gap={3}>
126
- <Field.Label>{intlLabel?.defaultMessage || 'Buttons'}</Field.Label>
127
- <Typography variant="pi" textColor="neutral600">
128
- Build Telegram inline keyboard buttons (text + URL). Stored as JSON.
129
- </Typography>
130
-
131
- <Flex direction="column" gap={2}>
132
- {buttons.map((btn, idx) => {
133
- const urlOk = btn.url.length === 0 ? true : isValidUrl(btn.url);
134
- return (
135
- <Flex
136
- key={btn.id}
137
- gap={2}
138
- alignItems="flex-start"
139
- padding={3}
140
- background="neutral0"
141
- hasRadius
142
- style={{ border: '1px solid #dcdce4' }}
143
- >
144
- <Box style={{ flex: 2, minWidth: 180 }}>
145
- <Field.Root name={`${name}.${btn.id}.text`}>
146
- <Field.Label>Text</Field.Label>
147
- <TextInput
148
- value={btn.text}
149
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
150
- updateButton(btn.id, { text: e.target.value })
151
- }
152
- disabled={disabled}
153
- />
154
- </Field.Root>
155
- </Box>
182
+ <Flex direction="column" gap={4}>
183
+ <Flex justifyContent="space-between" alignItems="center">
184
+ <Box>
185
+ <Field.Label>{intlLabel?.defaultMessage || 'Buttons'}</Field.Label>
186
+ <Typography variant="pi" textColor="neutral500">
187
+ Telegram inline keyboard buttons
188
+ </Typography>
189
+ </Box>
190
+ <Button
191
+ startIcon={<Plus />}
192
+ onClick={addButton}
193
+ disabled={disabled}
194
+ variant="secondary"
195
+ size="S"
196
+ >
197
+ Add
198
+ </Button>
199
+ </Flex>
156
200
 
157
- <Box style={{ flex: 3, minWidth: 220 }}>
158
- <Field.Root name={`${name}.${btn.id}.url`}>
159
- <Field.Label>URL</Field.Label>
160
- <TextInput
161
- value={btn.url}
162
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
163
- updateButton(btn.id, { url: e.target.value })
164
- }
165
- disabled={disabled}
166
- />
167
- {!urlOk && (
168
- <Typography variant="pi" textColor="danger600" style={{ marginTop: 4 }}>
169
- Please enter a valid http/https URL
170
- </Typography>
171
- )}
172
- </Field.Root>
173
- </Box>
201
+ {buttons.length === 0 ? (
202
+ <Box
203
+ padding={6}
204
+ background="neutral100"
205
+ hasRadius
206
+ style={{
207
+ border: `2px dashed ${colors.emptyState.border}`,
208
+ textAlign: 'center',
209
+ }}
210
+ >
211
+ <Typography variant="omega" textColor="neutral500">
212
+ No buttons yet
213
+ </Typography>
214
+ <Typography variant="pi" textColor="neutral400" style={{ display: 'block', marginTop: 4 }}>
215
+ Add a button to create an inline keyboard for your Telegram message
216
+ </Typography>
217
+ </Box>
218
+ ) : (
219
+ <Flex direction="column" gap={2}>
220
+ {buttons.map((btn, idx) => {
221
+ const urlOk = btn.url.length === 0 ? true : isValidUrl(btn.url);
222
+ return (
223
+ <Card
224
+ key={btn.id}
225
+ background="neutral0"
226
+ style={{
227
+ border: `1px solid ${urlOk ? colors.card.border : colors.card.borderError}`,
228
+ transition: 'box-shadow 0.15s ease',
229
+ }}
230
+ >
231
+ <CardContent>
232
+ <Flex gap={3} alignItems="center" padding={3}>
233
+ <Box
234
+ style={{
235
+ width: '24px',
236
+ height: '24px',
237
+ borderRadius: '4px',
238
+ backgroundColor: colors.indexBadge.background,
239
+ display: 'flex',
240
+ alignItems: 'center',
241
+ justifyContent: 'center',
242
+ flexShrink: 0,
243
+ }}
244
+ >
245
+ <Typography variant="sigma" textColor="primary600">
246
+ {idx + 1}
247
+ </Typography>
248
+ </Box>
174
249
 
175
- <Box style={{ width: 90 }}>
176
- <Field.Root name={`${name}.${btn.id}.row`}>
177
- <Field.Label>Row</Field.Label>
178
- <TextInput
179
- type="number"
180
- value={String(btn.row ?? 0)}
181
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
182
- updateButton(btn.id, { row: parseInt(e.target.value, 10) || 0 })
183
- }
184
- disabled={disabled}
185
- />
186
- </Field.Root>
187
- </Box>
250
+ <Box style={{ flex: 1, minWidth: 120 }}>
251
+ <TextInput
252
+ placeholder="Button text"
253
+ value={btn.text}
254
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
255
+ updateButton(btn.id, { text: e.target.value })
256
+ }
257
+ disabled={disabled}
258
+ size="S"
259
+ />
260
+ </Box>
188
261
 
189
- <Flex direction="column" gap={1} paddingTop={6}>
190
- <Tooltip label="Move up">
191
- <IconButton
192
- onClick={() => move(idx, idx - 1)}
193
- label="Move up"
194
- variant="ghost"
195
- disabled={disabled || idx === 0}
196
- >
197
- <ArrowUp />
198
- </IconButton>
199
- </Tooltip>
200
- <Tooltip label="Move down">
201
- <IconButton
202
- onClick={() => move(idx, idx + 1)}
203
- label="Move down"
204
- variant="ghost"
205
- disabled={disabled || idx === buttons.length - 1}
206
- >
207
- <ArrowDown />
208
- </IconButton>
209
- </Tooltip>
210
- <Tooltip label="Delete button">
211
- <IconButton
212
- onClick={() => deleteButton(btn.id)}
213
- label="Delete button"
214
- variant="ghost"
215
- disabled={disabled}
216
- >
217
- <Trash />
218
- </IconButton>
219
- </Tooltip>
220
- </Flex>
221
- </Flex>
222
- );
223
- })}
262
+ <Box style={{ flex: 2, minWidth: 180 }}>
263
+ <TextInput
264
+ placeholder="https://..."
265
+ value={btn.url}
266
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
267
+ updateButton(btn.id, { url: e.target.value })
268
+ }
269
+ disabled={disabled}
270
+ size="S"
271
+ hasError={!urlOk}
272
+ />
273
+ </Box>
224
274
 
225
- {buttons.length === 0 && (
226
- <Box padding={4} background="neutral0" hasRadius style={{ border: '1px dashed #dcdce4' }}>
227
- <Typography variant="omega" textColor="neutral600">
228
- No buttons. Add one to create an inline keyboard.
229
- </Typography>
230
- </Box>
231
- )}
232
- </Flex>
275
+ <Box style={{ width: 60 }}>
276
+ <TextInput
277
+ type="number"
278
+ placeholder="Row"
279
+ value={String(btn.row ?? 0)}
280
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
281
+ updateButton(btn.id, { row: parseInt(e.target.value, 10) || 0 })
282
+ }
283
+ disabled={disabled}
284
+ size="S"
285
+ />
286
+ </Box>
233
287
 
234
- <Flex gap={2}>
235
- <Button startIcon={<Plus />} onClick={addButton} disabled={disabled} variant="secondary">
236
- Add button
237
- </Button>
238
- </Flex>
288
+ <Flex gap={1} style={{ flexShrink: 0 }}>
289
+ <Tooltip label="Move up">
290
+ <IconButton
291
+ onClick={() => move(idx, idx - 1)}
292
+ label="Move up"
293
+ variant="ghost"
294
+ size="S"
295
+ disabled={disabled || idx === 0}
296
+ >
297
+ <ArrowUp width={16} height={16} />
298
+ </IconButton>
299
+ </Tooltip>
300
+ <Tooltip label="Move down">
301
+ <IconButton
302
+ onClick={() => move(idx, idx + 1)}
303
+ label="Move down"
304
+ variant="ghost"
305
+ size="S"
306
+ disabled={disabled || idx === buttons.length - 1}
307
+ >
308
+ <ArrowDown width={16} height={16} />
309
+ </IconButton>
310
+ </Tooltip>
311
+ <Tooltip label="Delete">
312
+ <IconButton
313
+ onClick={() => deleteButton(btn.id)}
314
+ label="Delete"
315
+ variant="ghost"
316
+ size="S"
317
+ disabled={disabled}
318
+ style={{ color: '#d02b20' }}
319
+ >
320
+ <Trash width={16} height={16} />
321
+ </IconButton>
322
+ </Tooltip>
323
+ </Flex>
324
+ </Flex>
325
+ {!urlOk && (
326
+ <Box paddingLeft={3} paddingBottom={2}>
327
+ <Typography variant="pi" textColor="danger600">
328
+ Please enter a valid http/https URL
329
+ </Typography>
330
+ </Box>
331
+ )}
332
+ </CardContent>
333
+ </Card>
334
+ );
335
+ })}
336
+ </Flex>
337
+ )}
239
338
 
240
339
  {buttons.length > 0 && (
241
- <Box paddingTop={2}>
242
- <Typography variant="pi" textColor="neutral600" style={{ marginBottom: 8, display: 'block' }}>
243
- Preview
340
+ <Box
341
+ padding={4}
342
+ hasRadius
343
+ style={{
344
+ backgroundImage: colors.preview.background,
345
+ }}
346
+ >
347
+ <Typography
348
+ variant="sigma"
349
+ textColor="neutral0"
350
+ style={{ display: 'block', marginBottom: 12, opacity: 0.7 }}
351
+ >
352
+ PREVIEW
244
353
  </Typography>
245
- <Flex direction="column" gap={2}>
354
+ <Flex direction="column" gap={2} alignItems="center">
246
355
  {previewRows.map((row) => (
247
- <Flex key={row.row} gap={1} wrap="wrap">
356
+ <Flex key={row.row} gap={2} wrap="wrap" justifyContent="center">
248
357
  {row.items.map((b) => (
249
- <Badge key={b.id} backgroundColor="primary100" textColor="primary600">
250
- {b.text || '(empty)'}
251
- </Badge>
358
+ <TelegramButtonPreview key={b.id} text={b.text} url={b.url} />
252
359
  ))}
253
360
  </Flex>
254
361
  ))}
@@ -267,4 +374,3 @@ const ButtonsBuilder = forwardRef<HTMLDivElement, ButtonsBuilderProps>(
267
374
  ButtonsBuilder.displayName = 'ButtonsBuilder';
268
375
 
269
376
  export default ButtonsBuilder;
270
-
@@ -2,38 +2,85 @@ import type { StepType, ChannelType, DurationUnit } from './types';
2
2
 
3
3
  export const NODE_COLORS: Record<
4
4
  StepType,
5
- { background: string; border: string; text: string }
5
+ {
6
+ background: string;
7
+ border: string;
8
+ text: string;
9
+ gradient: string;
10
+ glow: string;
11
+ icon: string;
12
+ }
6
13
  > = {
7
14
  entry: {
8
- background: '#eef5eb',
9
- border: '#5cb176',
10
- text: '#297c3b',
15
+ background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
16
+ border: '#059669',
17
+ text: '#ffffff',
18
+ gradient: 'linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%)',
19
+ glow: 'rgba(16, 185, 129, 0.25)',
20
+ icon: '#059669',
11
21
  },
12
22
  message: {
13
- background: '#eef8ff',
14
- border: '#0077cc',
15
- text: '#0055a4',
23
+ background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
24
+ border: '#2563eb',
25
+ text: '#ffffff',
26
+ gradient: 'linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%)',
27
+ glow: 'rgba(59, 130, 246, 0.25)',
28
+ icon: '#2563eb',
16
29
  },
17
30
  wait: {
18
- background: '#fffae6',
19
- border: '#e9b200',
20
- text: '#a16207',
31
+ background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
32
+ border: '#d97706',
33
+ text: '#ffffff',
34
+ gradient: 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)',
35
+ glow: 'rgba(245, 158, 11, 0.25)',
36
+ icon: '#d97706',
21
37
  },
22
38
  branch: {
23
- background: '#f5f0ff',
24
- border: '#7b61ff',
25
- text: '#5746af',
39
+ background: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)',
40
+ border: '#7c3aed',
41
+ text: '#ffffff',
42
+ gradient: 'linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%)',
43
+ glow: 'rgba(139, 92, 246, 0.25)',
44
+ icon: '#7c3aed',
26
45
  },
27
46
  exit: {
28
- background: '#fef1f0',
29
- border: '#dc2626',
30
- text: '#c03030',
47
+ background: 'linear-gradient(135deg, #6b7280 0%, #4b5563 100%)',
48
+ border: '#4b5563',
49
+ text: '#ffffff',
50
+ gradient: 'linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%)',
51
+ glow: 'rgba(107, 114, 128, 0.25)',
52
+ icon: '#4b5563',
53
+ },
54
+ };
55
+
56
+ export const EDGE_COLORS = {
57
+ default: {
58
+ stroke: '#94a3b8',
59
+ strokeWidth: 2,
60
+ },
61
+ yes: {
62
+ stroke: '#10b981',
63
+ strokeWidth: 2,
64
+ label: {
65
+ background: '#d1fae5',
66
+ text: '#059669',
67
+ border: '#a7f3d0',
68
+ },
69
+ },
70
+ no: {
71
+ stroke: '#ef4444',
72
+ strokeWidth: 2,
73
+ label: {
74
+ background: '#fee2e2',
75
+ text: '#dc2626',
76
+ border: '#fecaca',
77
+ },
31
78
  },
32
79
  };
33
80
 
34
81
  export const NODE_DIMENSIONS = {
35
- width: 220,
36
- height: 80,
82
+ width: 240,
83
+ height: 88,
37
84
  };
38
85
 
39
86
  export const STEP_TYPE_LABELS: Record<StepType, string> = {
@@ -2,16 +2,16 @@ import React from 'react';
2
2
  import {
3
3
  BaseEdge,
4
4
  EdgeLabelRenderer,
5
- getBezierPath,
5
+ getSmoothStepPath,
6
6
  type EdgeProps,
7
7
  } from 'reactflow';
8
+ import { EDGE_COLORS } from '../constants';
8
9
 
9
10
  interface LabeledEdgeData {
10
11
  label?: string;
11
12
  }
12
13
 
13
14
  const LabeledEdge: React.FC<EdgeProps<LabeledEdgeData>> = ({
14
- id,
15
15
  sourceX,
16
16
  sourceY,
17
17
  targetX,
@@ -22,21 +22,26 @@ const LabeledEdge: React.FC<EdgeProps<LabeledEdgeData>> = ({
22
22
  style = {},
23
23
  markerEnd,
24
24
  }) => {
25
- const [edgePath, labelX, labelY] = getBezierPath({
25
+ const [edgePath] = getSmoothStepPath({
26
26
  sourceX,
27
27
  sourceY,
28
28
  sourcePosition,
29
29
  targetX,
30
30
  targetY,
31
31
  targetPosition,
32
+ borderRadius: 16,
32
33
  });
33
34
 
34
35
  const isYes = sourceHandle === 'yes';
35
36
  const isNo = sourceHandle === 'no';
36
- const label = isYes ? 'Yes' : isNo ? 'No' : null;
37
- const bgColor = isYes ? '#dcfce7' : isNo ? '#fef2f2' : '#fff';
38
- const textColor = isYes ? '#15803d' : isNo ? '#dc2626' : '#333';
39
- const borderColor = isYes ? '#86efac' : isNo ? '#fca5a5' : '#ddd';
37
+ const label = isYes ? 'YES' : isNo ? 'NO' : null;
38
+
39
+ const edgeStyle = isYes ? EDGE_COLORS.yes : isNo ? EDGE_COLORS.no : EDGE_COLORS.default;
40
+
41
+ const labelStyle = isYes ? EDGE_COLORS.yes.label : isNo ? EDGE_COLORS.no.label : null;
42
+
43
+ const labelPosX = sourceX;
44
+ const labelPosY = sourceY + 24;
40
45
 
41
46
  return (
42
47
  <>
@@ -45,23 +50,25 @@ const LabeledEdge: React.FC<EdgeProps<LabeledEdgeData>> = ({
45
50
  markerEnd={markerEnd}
46
51
  style={{
47
52
  ...style,
48
- stroke: isYes ? '#5cb176' : isNo ? '#dc2626' : '#b1b1b7',
49
- strokeWidth: 2,
53
+ stroke: edgeStyle.stroke,
54
+ strokeWidth: edgeStyle.strokeWidth,
50
55
  }}
51
56
  />
52
- {label && (
57
+ {label && labelStyle && (
53
58
  <EdgeLabelRenderer>
54
59
  <div
55
60
  style={{
56
61
  position: 'absolute',
57
- transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
58
- fontSize: 11,
59
- fontWeight: 600,
60
- background: bgColor,
61
- color: textColor,
62
- padding: '2px 8px',
63
- borderRadius: 4,
64
- border: `1px solid ${borderColor}`,
62
+ transform: `translate(-50%, -50%) translate(${labelPosX}px, ${labelPosY}px)`,
63
+ fontSize: 10,
64
+ fontWeight: 700,
65
+ letterSpacing: '0.05em',
66
+ background: labelStyle.background,
67
+ color: labelStyle.text,
68
+ padding: '4px 12px',
69
+ borderRadius: 6,
70
+ border: `2px solid ${labelStyle.border}`,
71
+ boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
65
72
  pointerEvents: 'all',
66
73
  }}
67
74
  className="nodrag nopan"
@@ -220,6 +220,7 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({ steps, onStepsChange, disabled,
220
220
  <Box
221
221
  style={{
222
222
  height: '100%',
223
+ width: '100%',
223
224
  border: error ? '1px solid #ee5e52' : '1px solid #dcdce4',
224
225
  borderRadius: 4,
225
226
  overflow: 'hidden',
@@ -0,0 +1,50 @@
1
+ import { useTheme } from 'styled-components';
2
+
3
+ interface StrapiTheme {
4
+ colors?: Record<string, string>;
5
+ }
6
+
7
+ export interface FlowThemeColors {
8
+ background: string;
9
+ surface: string;
10
+ surfaceHover: string;
11
+ border: string;
12
+ borderLight: string;
13
+ text: string;
14
+ textMuted: string;
15
+ textSubtle: string;
16
+ isDark: boolean;
17
+ }
18
+
19
+ export const useFlowTheme = (): FlowThemeColors => {
20
+ const theme = useTheme() as StrapiTheme;
21
+ const colors = theme?.colors;
22
+
23
+ const isDark = colors?.neutral0 === '#212134';
24
+
25
+ if (isDark) {
26
+ return {
27
+ background: '#212134',
28
+ surface: '#262639',
29
+ surfaceHover: '#32324d',
30
+ border: '#4a4a6a',
31
+ borderLight: '#3d3d57',
32
+ text: '#ffffff',
33
+ textMuted: '#a5a5ba',
34
+ textSubtle: '#8e8ea9',
35
+ isDark: true,
36
+ };
37
+ }
38
+
39
+ return {
40
+ background: '#ffffff',
41
+ surface: '#f8fafc',
42
+ surfaceHover: '#f1f5f9',
43
+ border: '#e2e8f0',
44
+ borderLight: '#f1f5f9',
45
+ text: '#1e293b',
46
+ textMuted: '#64748b',
47
+ textSubtle: '#94a3b8',
48
+ isDark: false,
49
+ };
50
+ };