@elementor/editor-variables 3.35.3 → 3.35.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-variables",
3
- "version": "3.35.3",
3
+ "version": "3.35.5",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,22 +39,22 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "3.35.3",
43
- "@elementor/editor-canvas": "3.35.3",
44
- "@elementor/editor-controls": "3.35.3",
45
- "@elementor/editor-current-user": "3.35.3",
46
- "@elementor/editor-editing-panel": "3.35.3",
47
- "@elementor/editor-mcp": "3.35.3",
48
- "@elementor/editor-panels": "3.35.3",
49
- "@elementor/editor-props": "3.35.3",
50
- "@elementor/editor-ui": "3.35.3",
51
- "@elementor/editor-v1-adapters": "3.35.3",
52
- "@elementor/http-client": "3.35.3",
42
+ "@elementor/editor": "3.35.5",
43
+ "@elementor/editor-canvas": "3.35.5",
44
+ "@elementor/editor-controls": "3.35.5",
45
+ "@elementor/editor-current-user": "3.35.5",
46
+ "@elementor/editor-editing-panel": "3.35.5",
47
+ "@elementor/editor-mcp": "3.35.5",
48
+ "@elementor/editor-panels": "3.35.5",
49
+ "@elementor/editor-props": "3.35.5",
50
+ "@elementor/editor-ui": "3.35.5",
51
+ "@elementor/editor-v1-adapters": "3.35.5",
52
+ "@elementor/http-client": "3.35.5",
53
53
  "@elementor/icons": "^1.63.0",
54
- "@elementor/events": "3.35.3",
55
- "@elementor/schema": "3.35.3",
54
+ "@elementor/events": "3.35.5",
55
+ "@elementor/schema": "3.35.5",
56
56
  "@elementor/ui": "1.36.17",
57
- "@elementor/utils": "3.35.3",
57
+ "@elementor/utils": "3.35.5",
58
58
  "@wordpress/i18n": "^5.13.0"
59
59
  },
60
60
  "peerDependencies": {
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { type MouseEvent } from 'react';
2
3
  import { EllipsisWithTooltip, type VirtualizedItem } from '@elementor/editor-ui';
3
4
  import { EditIcon } from '@elementor/icons';
4
5
  import { Box, IconButton, ListItemIcon, Tooltip, Typography } from '@elementor/ui';
@@ -7,12 +8,17 @@ import { __ } from '@wordpress/i18n';
7
8
  const SIZE = 'tiny';
8
9
  const EDIT_LABEL = __( 'Edit variable', 'elementor' );
9
10
 
10
- export const MenuItemContent = < T, V extends string >( { item }: { item: VirtualizedItem< T, V > } ) => {
11
+ type MenuItemContentProps< T, V extends string > = {
12
+ item: VirtualizedItem< T, V >;
13
+ disabled?: boolean;
14
+ };
15
+
16
+ export const MenuItemContent = < T, V extends string >( { item, disabled = false }: MenuItemContentProps< T, V > ) => {
11
17
  const onEdit = item.onEdit as ( ( value: V ) => void ) | undefined;
12
18
 
13
19
  return (
14
20
  <>
15
- <ListItemIcon>{ item.icon }</ListItemIcon>
21
+ <ListItemIcon sx={ { color: disabled ? 'text.disabled' : 'inherit' } }>{ item.icon }</ListItemIcon>
16
22
  <Box
17
23
  sx={ {
18
24
  flex: 1,
@@ -26,7 +32,7 @@ export const MenuItemContent = < T, V extends string >( { item }: { item: Virtua
26
32
  title={ item.label || item.value }
27
33
  as={ Typography }
28
34
  variant="caption"
29
- color="text.primary"
35
+ color={ disabled ? 'text.disabled' : 'text.primary' }
30
36
  sx={ { marginTop: '1px', lineHeight: '2' } }
31
37
  maxWidth="50%"
32
38
  />
@@ -35,17 +41,17 @@ export const MenuItemContent = < T, V extends string >( { item }: { item: Virtua
35
41
  title={ item.secondaryText }
36
42
  as={ Typography }
37
43
  variant="caption"
38
- color="text.tertiary"
44
+ color={ disabled ? 'text.disabled' : 'text.tertiary' }
39
45
  sx={ { marginTop: '1px', lineHeight: '1' } }
40
46
  maxWidth="50%"
41
47
  />
42
48
  ) }
43
49
  </Box>
44
- { !! onEdit && (
50
+ { !! onEdit && ! disabled && (
45
51
  <Tooltip placement="top" title={ EDIT_LABEL }>
46
52
  <IconButton
47
53
  sx={ { mx: 1, opacity: '0' } }
48
- onClick={ ( e: React.MouseEvent< HTMLButtonElement > ) => {
54
+ onClick={ ( e: MouseEvent< HTMLButtonElement > ) => {
49
55
  e.stopPropagation();
50
56
  onEdit( item.value );
51
57
  } }
@@ -1,6 +1,6 @@
1
1
  import { MenuList, styled } from '@elementor/ui';
2
2
 
3
- export const VariablesStyledMenuList = styled( MenuList )( ( { theme } ) => ( {
3
+ export const VariablesStyledMenuList = styled( MenuList )< { disabled?: boolean } >( ( { theme, disabled } ) => ( {
4
4
  '& > li': {
5
5
  height: 32,
6
6
  width: '100%',
@@ -11,13 +11,15 @@ export const VariablesStyledMenuList = styled( MenuList )( ( { theme } ) => ( {
11
11
  ...theme.typography.caption,
12
12
  lineHeight: 'inherit',
13
13
  padding: theme.spacing( 0.5, 1, 0.5, 2 ),
14
- '&:hover, &:focus': {
15
- backgroundColor: theme.palette.action.hover,
16
- },
14
+ ...( ! disabled && {
15
+ '&:hover, &:focus': {
16
+ backgroundColor: theme.palette.action.hover,
17
+ },
18
+ cursor: 'pointer',
19
+ } ),
17
20
  '&[aria-selected="true"]': {
18
21
  backgroundColor: theme.palette.action.selected,
19
22
  },
20
- cursor: 'pointer',
21
23
  textOverflow: 'ellipsis',
22
24
  position: 'absolute',
23
25
  top: 0,
@@ -0,0 +1,63 @@
1
+ import * as React from 'react';
2
+ import { forwardRef, type MouseEvent, useImperativeHandle, useState } from 'react';
3
+ import { PromotionChip, PromotionPopover, useCanvasClickHandler } from '@elementor/editor-ui';
4
+ import { Box } from '@elementor/ui';
5
+ import { capitalize } from '@elementor/utils';
6
+ import { __, sprintf } from '@wordpress/i18n';
7
+
8
+ type VariablePromotionChipProps = {
9
+ variableType: string;
10
+ upgradeUrl: string;
11
+ };
12
+
13
+ export type VariablePromotionChipRef = {
14
+ toggle: () => void;
15
+ };
16
+
17
+ export const VariablePromotionChip = forwardRef< VariablePromotionChipRef, VariablePromotionChipProps >(
18
+ ( { variableType, upgradeUrl }, ref ) => {
19
+ const [ isOpen, setIsOpen ] = useState( false );
20
+
21
+ useCanvasClickHandler( isOpen, () => setIsOpen( false ) );
22
+
23
+ const toggle = () => setIsOpen( ( prev ) => ! prev );
24
+
25
+ useImperativeHandle( ref, () => ( { toggle } ), [] );
26
+
27
+ const title = sprintf(
28
+ /* translators: %s: Variable Type. */
29
+ __( '%s variables', 'elementor' ),
30
+ capitalize( variableType )
31
+ );
32
+
33
+ const content = sprintf(
34
+ /* translators: %s: Variable Type. */
35
+ __( 'Upgrade to continue creating and editing %s variables.', 'elementor' ),
36
+ variableType
37
+ );
38
+
39
+ return (
40
+ <PromotionPopover
41
+ open={ isOpen }
42
+ title={ title }
43
+ content={ content }
44
+ ctaText={ __( 'Upgrade now', 'elementor' ) }
45
+ ctaUrl={ upgradeUrl }
46
+ onClose={ ( e: MouseEvent ) => {
47
+ e.stopPropagation();
48
+ setIsOpen( false );
49
+ } }
50
+ >
51
+ <Box
52
+ onClick={ ( e: MouseEvent ) => {
53
+ e.stopPropagation();
54
+ toggle();
55
+ } }
56
+ sx={ { cursor: 'pointer', display: 'inline-flex' } }
57
+ >
58
+ <PromotionChip />
59
+ </Box>
60
+ </PromotionPopover>
61
+ );
62
+ }
63
+ );
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { createElement } from 'react';
2
+ import { createElement, type MouseEvent } from 'react';
3
3
  import { DotsVerticalIcon } from '@elementor/icons';
4
4
  import { bindMenu, bindTrigger, IconButton, Menu, MenuItem, type SvgIconProps, usePopupState } from '@elementor/ui';
5
5
 
@@ -21,9 +21,19 @@ export const VariableEditMenu = ( { menuActions, disabled, itemId }: VariableEdi
21
21
  variant: 'popover',
22
22
  } );
23
23
 
24
+ const triggerProps = bindTrigger( menuState );
25
+
24
26
  return (
25
27
  <>
26
- <IconButton { ...bindTrigger( menuState ) } disabled={ disabled } size="tiny">
28
+ <IconButton
29
+ { ...triggerProps }
30
+ disabled={ disabled }
31
+ size="tiny"
32
+ onClick={ ( e: MouseEvent ) => {
33
+ e.stopPropagation();
34
+ triggerProps.onClick?.( e );
35
+ } }
36
+ >
27
37
  <DotsVerticalIcon fontSize="tiny" />
28
38
  </IconButton>
29
39
 
@@ -51,7 +61,8 @@ export const VariableEditMenu = ( { menuActions, disabled, itemId }: VariableEdi
51
61
  { menuActions.map( ( action ) => (
52
62
  <MenuItem
53
63
  key={ action.name }
54
- onClick={ () => {
64
+ onClick={ ( e: MouseEvent ) => {
65
+ e.stopPropagation();
55
66
  action.onClick?.( itemId );
56
67
  menuState.close();
57
68
  } }
@@ -0,0 +1,210 @@
1
+ import * as React from 'react';
2
+ import { createElement, useRef } from 'react';
3
+ import { EllipsisWithTooltip } from '@elementor/editor-ui';
4
+ import { GripVerticalIcon } from '@elementor/icons';
5
+ import { IconButton, Stack, TableRow, type UnstableSortableItemRenderProps } from '@elementor/ui';
6
+
7
+ import { useQuotaPermissions } from '../../../hooks/use-quota-permissions';
8
+ import { type TVariablesList } from '../../../storage';
9
+ import { type getVariableType } from '../../../variables-registry/variable-type-registry';
10
+ import { LabelField } from '../../fields/label-field';
11
+ import { VariablePromotionChip, type VariablePromotionChipRef } from '../../ui/variable-promotion-chip';
12
+ import { VariableEditableCell } from '../variable-editable-cell';
13
+ import { VariableEditMenu, type VariableManagerMenuAction } from './variable-edit-menu';
14
+ import { VariableTableCell } from './variable-table-cell';
15
+
16
+ export type Row = ReturnType< typeof getVariableType > & {
17
+ id: string;
18
+ type: string;
19
+ name: string;
20
+ value: string;
21
+ };
22
+ export const VariableRow = (
23
+ props: UnstableSortableItemRenderProps & {
24
+ row: Row;
25
+ variables: TVariablesList;
26
+ handleOnChange: ( variables: TVariablesList ) => void;
27
+ autoEditVariableId?: string;
28
+ onAutoEditComplete?: () => void;
29
+ onFieldError?: ( hasError: boolean ) => void;
30
+ menuActions: ( variableId: string ) => VariableManagerMenuAction[];
31
+ handleRowRef: ( id: string ) => ( ref: HTMLTableRowElement | null ) => void;
32
+ }
33
+ ) => {
34
+ const {
35
+ row,
36
+ variables,
37
+ handleOnChange,
38
+ autoEditVariableId,
39
+ onAutoEditComplete,
40
+ onFieldError,
41
+ menuActions,
42
+ handleRowRef,
43
+ itemProps,
44
+ showDropIndication,
45
+ triggerProps,
46
+ itemStyle,
47
+ triggerStyle,
48
+ isDragged,
49
+ dropPosition,
50
+ setTriggerRef,
51
+ isSorting,
52
+ } = props;
53
+ const promotionRef = useRef< VariablePromotionChipRef >( null );
54
+ const isDisabled = ! useQuotaPermissions( row.type ).canEdit();
55
+
56
+ const showIndicationBefore = showDropIndication && dropPosition === 'before';
57
+ const showIndicationAfter = showDropIndication && dropPosition === 'after';
58
+
59
+ return (
60
+ <TableRow
61
+ { ...itemProps }
62
+ ref={ itemProps.ref }
63
+ selected={ isDragged }
64
+ sx={ {
65
+ ...( isDisabled && {
66
+ '& td, & th': {
67
+ color: 'text.disabled',
68
+ },
69
+ } ),
70
+ ...( showIndicationBefore && {
71
+ '& td, & th': {
72
+ borderTop: '2px solid',
73
+ borderTopColor: 'primary.main',
74
+ },
75
+ } ),
76
+ ...( showIndicationAfter && {
77
+ '& td, & th': {
78
+ borderBottom: '2px solid',
79
+ borderBottomColor: 'primary.main',
80
+ },
81
+ } ),
82
+ '&:hover, &:focus-within': {
83
+ backgroundColor: 'action.hover',
84
+ '& [role="toolbar"], & [draggable]': {
85
+ opacity: 1,
86
+ },
87
+ },
88
+ '& [role="toolbar"], & [draggable]': {
89
+ opacity: 0,
90
+ },
91
+ } }
92
+ style={ { ...itemStyle, ...triggerStyle } }
93
+ onClick={ () => {
94
+ if ( isDisabled ) {
95
+ promotionRef.current?.toggle();
96
+ }
97
+ } }
98
+ >
99
+ <VariableTableCell noPadding width={ 10 } maxWidth={ 10 }>
100
+ <IconButton size="small" ref={ setTriggerRef } { ...triggerProps } disabled={ isSorting } draggable>
101
+ <GripVerticalIcon fontSize="inherit" />
102
+ </IconButton>
103
+ </VariableTableCell>
104
+ <VariableTableCell>
105
+ <VariableEditableCell
106
+ initialValue={ row.name }
107
+ onChange={ ( value ) => {
108
+ if ( value !== row.name && ! isDisabled ) {
109
+ handleOnChange( {
110
+ ...variables,
111
+ [ row.id ]: { ...variables[ row.id ], label: value },
112
+ } );
113
+ }
114
+ } }
115
+ prefixElement={ createElement( row.icon, {
116
+ fontSize: 'inherit',
117
+ color: isDisabled ? 'disabled' : 'inherit',
118
+ } ) }
119
+ editableElement={ ( { value, onChange, onValidationChange, error } ) => (
120
+ <LabelField
121
+ id={ 'variable-label-' + row.id }
122
+ size="tiny"
123
+ value={ value }
124
+ onChange={ onChange }
125
+ onErrorChange={ ( errorMsg ) => {
126
+ onValidationChange?.( errorMsg );
127
+ onFieldError?.( !! errorMsg );
128
+ } }
129
+ error={ error }
130
+ focusOnShow
131
+ selectOnShow={ autoEditVariableId === row.id }
132
+ showWarningInfotip={ true }
133
+ variables={ variables }
134
+ />
135
+ ) }
136
+ autoEdit={ autoEditVariableId === row.id && ! isDisabled }
137
+ onRowRef={ handleRowRef( row.id ) }
138
+ onAutoEditComplete={ autoEditVariableId === row.id ? onAutoEditComplete : undefined }
139
+ fieldType="label"
140
+ disabled={ isDisabled }
141
+ >
142
+ <EllipsisWithTooltip title={ row.name } sx={ { border: '4px solid transparent' } }>
143
+ { row.name }
144
+ </EllipsisWithTooltip>
145
+ </VariableEditableCell>
146
+ </VariableTableCell>
147
+ <VariableTableCell>
148
+ <VariableEditableCell
149
+ initialValue={ row.value }
150
+ onChange={ ( value ) => {
151
+ if ( value !== row.value && ! isDisabled ) {
152
+ handleOnChange( {
153
+ ...variables,
154
+ [ row.id ]: { ...variables[ row.id ], value },
155
+ } );
156
+ }
157
+ } }
158
+ editableElement={ ( { value, onChange, onValidationChange, error } ) =>
159
+ row.valueField?.( {
160
+ value,
161
+ onChange,
162
+ onPropTypeKeyChange: ( type ) => {
163
+ if ( ! isDisabled ) {
164
+ handleOnChange( {
165
+ ...variables,
166
+ [ row.id ]: { ...variables[ row.id ], type },
167
+ } );
168
+ }
169
+ },
170
+ propTypeKey: row.type,
171
+ onValidationChange: ( errorMsg ) => {
172
+ onValidationChange?.( errorMsg );
173
+ onFieldError?.( !! errorMsg );
174
+ },
175
+ error,
176
+ } ) ?? <></>
177
+ }
178
+ onRowRef={ handleRowRef( row.id ) }
179
+ gap={ 0.25 }
180
+ fieldType="value"
181
+ disabled={ isDisabled }
182
+ >
183
+ { row.startIcon && row.startIcon( { value: row.value } ) }
184
+ <EllipsisWithTooltip
185
+ title={ row.value }
186
+ sx={ {
187
+ border: '4px solid transparent',
188
+ lineHeight: '1',
189
+ pt: 0.25,
190
+ } }
191
+ >
192
+ { row.value }
193
+ </EllipsisWithTooltip>
194
+ </VariableEditableCell>
195
+ </VariableTableCell>
196
+ <VariableTableCell align="right" noPadding width={ 16 } maxWidth={ 16 } sx={ { paddingInlineEnd: 1 } }>
197
+ <Stack role="toolbar" direction="row" justifyContent="flex-end" alignItems="center">
198
+ { isDisabled && (
199
+ <VariablePromotionChip
200
+ variableType={ row.variableType }
201
+ upgradeUrl={ `https://go.elementor.com/renew-license-manager-${ row.variableType }-variable` }
202
+ ref={ promotionRef }
203
+ />
204
+ ) }
205
+ <VariableEditMenu menuActions={ menuActions( row.id ) } disabled={ isSorting } itemId={ row.id } />
206
+ </Stack>
207
+ </VariableTableCell>
208
+ </TableRow>
209
+ );
210
+ };
@@ -16,6 +16,7 @@ type VariableEditableCellProps = {
16
16
  onAutoEditComplete?: () => void;
17
17
  gap?: number;
18
18
  fieldType?: 'label' | 'value';
19
+ disabled?: boolean;
19
20
  };
20
21
 
21
22
  export const VariableEditableCell = React.memo(
@@ -30,6 +31,7 @@ export const VariableEditableCell = React.memo(
30
31
  onAutoEditComplete,
31
32
  gap = 1,
32
33
  fieldType,
34
+ disabled = false,
33
35
  }: VariableEditableCellProps ) => {
34
36
  const [ value, setValue ] = useState( initialValue );
35
37
  const [ isEditing, setIsEditing ] = useState( false );
@@ -54,17 +56,23 @@ export const VariableEditableCell = React.memo(
54
56
  }, [ onRowRef ] );
55
57
 
56
58
  useEffect( () => {
57
- if ( autoEdit && ! isEditing ) {
59
+ if ( autoEdit && ! isEditing && ! disabled ) {
58
60
  setIsEditing( true );
59
61
  onAutoEditComplete?.();
60
62
  }
61
- }, [ autoEdit, isEditing, onAutoEditComplete ] );
63
+ }, [ autoEdit, isEditing, onAutoEditComplete, disabled ] );
62
64
 
63
65
  const handleDoubleClick = () => {
66
+ if ( disabled ) {
67
+ return;
68
+ }
64
69
  setIsEditing( true );
65
70
  };
66
71
 
67
72
  const handleKeyDown = ( event: React.KeyboardEvent< HTMLDivElement > ) => {
73
+ if ( disabled ) {
74
+ return;
75
+ }
68
76
  if ( event.key === 'Enter' ) {
69
77
  handleSave();
70
78
  } else if ( event.key === 'Escape' ) {
@@ -137,9 +145,9 @@ export const VariableEditableCell = React.memo(
137
145
  gap={ gap }
138
146
  onDoubleClick={ handleDoubleClick }
139
147
  onKeyDown={ handleKeyDown }
140
- tabIndex={ 0 }
148
+ tabIndex={ disabled ? -1 : 0 }
141
149
  role="button"
142
- aria-label="Double click or press Space to edit"
150
+ aria-label={ disabled ? '' : 'Double click or press Space to edit' }
143
151
  >
144
152
  { prefixElement }
145
153
  { children }
@@ -1,15 +1,15 @@
1
1
  import * as React from 'react';
2
- import { createElement, useMemo, useRef, useState } from 'react';
3
- import { PromotionChip, PromotionPopover } from '@elementor/editor-ui';
2
+ import { createElement, useMemo, useRef } from 'react';
4
3
  import { PlusIcon } from '@elementor/icons';
5
4
  import { bindMenu, bindTrigger, IconButton, Menu, MenuItem, type PopupState, Typography } from '@elementor/ui';
6
5
  import { capitalize } from '@elementor/utils';
7
- import { __, sprintf } from '@wordpress/i18n';
6
+ import { __ } from '@wordpress/i18n';
8
7
 
9
8
  import { useQuotaPermissions } from '../../hooks/use-quota-permissions';
10
9
  import { type TVariablesList } from '../../storage';
11
10
  import { trackVariablesManagerEvent } from '../../utils/tracking';
12
11
  import { getVariableTypes } from '../../variables-registry/variable-type-registry';
12
+ import { VariablePromotionChip, type VariablePromotionChipRef } from '../ui/variable-promotion-chip';
13
13
 
14
14
  export const SIZE = 'tiny';
15
15
 
@@ -28,12 +28,7 @@ type VariableManagerCreateMenuProps = {
28
28
  menuState: PopupState;
29
29
  };
30
30
 
31
- export const VariableManagerCreateMenu = ( {
32
- variables,
33
- onCreate,
34
- disabled,
35
- menuState,
36
- }: VariableManagerCreateMenuProps ) => {
31
+ export const VariableManagerCreateMenu = ( { variables, onCreate, menuState }: VariableManagerCreateMenuProps ) => {
37
32
  const buttonRef = useRef< HTMLButtonElement >( null );
38
33
 
39
34
  const variableTypes = getVariableTypes();
@@ -57,7 +52,6 @@ export const VariableManagerCreateMenu = ( {
57
52
  <IconButton
58
53
  { ...bindTrigger( menuState ) }
59
54
  ref={ buttonRef }
60
- disabled={ disabled }
61
55
  size={ SIZE }
62
56
  aria-label={ __( 'Add variable', 'elementor' ) }
63
57
  >
@@ -109,7 +103,7 @@ const MenuOption = ( {
109
103
  onCreate: VariableManagerCreateMenuProps[ 'onCreate' ];
110
104
  onClose: () => void;
111
105
  } ) => {
112
- const [ isPopoverOpen, setIsPopoverOpen ] = useState( false );
106
+ const promotionRef = useRef< VariablePromotionChipRef >( null );
113
107
  const userQuotaPermissions = useQuotaPermissions( config.propTypeKey );
114
108
 
115
109
  const displayName = capitalize( config.variableType );
@@ -117,9 +111,7 @@ const MenuOption = ( {
117
111
 
118
112
  const handleClick = () => {
119
113
  if ( isDisabled ) {
120
- if ( ! isPopoverOpen ) {
121
- setIsPopoverOpen( true );
122
- }
114
+ promotionRef.current?.toggle();
123
115
  return;
124
116
  }
125
117
 
@@ -130,18 +122,6 @@ const MenuOption = ( {
130
122
  onClose();
131
123
  };
132
124
 
133
- const title = sprintf(
134
- /* translators: %s: Variable Type. */
135
- __( '%s variables', 'elementor' ),
136
- capitalize( config.variableType )
137
- );
138
-
139
- const content = sprintf(
140
- /* translators: %s: Variable Type. */
141
- __( 'Upgrade to continue creating and editing %s variables.', 'elementor' ),
142
- config.variableType
143
- );
144
-
145
125
  return (
146
126
  <MenuItem onClick={ handleClick } sx={ { gap: 1.5, cursor: 'pointer' } }>
147
127
  { createElement( config.icon, { fontSize: SIZE, color: isDisabled ? 'disabled' : 'action' } ) }
@@ -149,18 +129,11 @@ const MenuOption = ( {
149
129
  { displayName }
150
130
  </Typography>
151
131
  { isDisabled && (
152
- <PromotionPopover
153
- open={ isPopoverOpen }
154
- title={ title }
155
- content={ content }
156
- ctaText={ __( 'Upgrade now', 'elementor' ) }
157
- ctaUrl={ `https://go.elementor.com/go-pro-manager-${ config.variableType }-variable/` }
158
- onClose={ () => {
159
- setIsPopoverOpen( false );
160
- } }
161
- >
162
- <PromotionChip />
163
- </PromotionPopover>
132
+ <VariablePromotionChip
133
+ variableType={ config.variableType }
134
+ upgradeUrl={ `https://go.elementor.com/go-pro-manager-${ config.variableType }-variable/` }
135
+ ref={ promotionRef }
136
+ />
164
137
  ) }
165
138
  </MenuItem>
166
139
  );
@@ -139,22 +139,25 @@ export function VariablesManagerPanel() {
139
139
  [ handleDeleteVariable ]
140
140
  );
141
141
 
142
- const menuActions = [
143
- {
144
- name: __( 'Delete', 'elementor' ),
145
- icon: TrashIcon,
146
- color: 'error.main',
147
- onClick: ( itemId: string ) => {
148
- const variable = variables[ itemId ];
149
- if ( variable ) {
150
- setDeleteConfirmation( { id: itemId, label: variable.label } );
142
+ const buildMenuActions = useCallback(
143
+ () => [
144
+ {
145
+ name: __( 'Delete', 'elementor' ),
146
+ icon: TrashIcon,
147
+ color: 'error.main',
148
+ onClick: ( itemId: string ) => {
149
+ const variable = variables[ itemId ];
150
+ if ( variable ) {
151
+ setDeleteConfirmation( { id: itemId, label: variable.label } );
151
152
 
152
- const variableTypeOptions = getVariableType( variable.type );
153
- trackVariablesManagerEvent( { action: 'delete', varType: variableTypeOptions?.variableType } );
154
- }
153
+ const variableTypeOptions = getVariableType( variable.type );
154
+ trackVariablesManagerEvent( { action: 'delete', varType: variableTypeOptions?.variableType } );
155
+ }
156
+ },
155
157
  },
156
- },
157
- ];
158
+ ],
159
+ [ variables ]
160
+ );
158
161
 
159
162
  const hasVariables = Object.keys( variables ).length > 0;
160
163
 
@@ -212,7 +215,7 @@ export function VariablesManagerPanel() {
212
215
  >
213
216
  { hasVariables && (
214
217
  <VariablesManagerTable
215
- menuActions={ menuActions }
218
+ menuActions={ buildMenuActions }
216
219
  variables={ variables }
217
220
  onChange={ handleOnChange }
218
221
  autoEditVariableId={ autoEditVariableId }