@elementor/editor-variables 0.13.0 → 0.15.0

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 (36) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/index.js +938 -344
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +950 -335
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +11 -10
  7. package/src/components/color-variable-creation.tsx +39 -16
  8. package/src/components/color-variable-edit.tsx +133 -0
  9. package/src/components/color-variables-selection.tsx +111 -52
  10. package/src/components/font-variable-creation.tsx +30 -16
  11. package/src/components/font-variable-edit.tsx +163 -0
  12. package/src/components/font-variables-selection.tsx +110 -51
  13. package/src/components/{color-indicator.tsx → ui/color-indicator.tsx} +1 -0
  14. package/src/components/ui/menu-item-content.tsx +60 -0
  15. package/src/components/ui/no-search-results.tsx +36 -0
  16. package/src/components/ui/no-variables.tsx +36 -0
  17. package/src/components/ui/styled-menu-list.tsx +31 -0
  18. package/src/components/ui/variable-tag.tsx +43 -0
  19. package/src/components/variable-selection-popover.context.ts +7 -0
  20. package/src/components/variable-selection-popover.tsx +142 -0
  21. package/src/components/variables-repeater-item-slot.tsx +29 -0
  22. package/src/controls/color-variable-control.tsx +66 -0
  23. package/src/controls/font-variable-control.tsx +60 -0
  24. package/src/create-style-variables-repository.ts +3 -2
  25. package/src/hooks/use-prop-color-variable-action.tsx +7 -2
  26. package/src/hooks/use-prop-font-variable-action.tsx +7 -2
  27. package/src/hooks/use-prop-variables.ts +34 -13
  28. package/src/init-color-variables.ts +51 -3
  29. package/src/init-font-variables.ts +2 -2
  30. package/src/service.ts +23 -3
  31. package/src/storage.ts +5 -1
  32. package/src/types.ts +12 -8
  33. package/src/components/styled-menu-item.tsx +0 -10
  34. package/src/components/variables-selection-popover.tsx +0 -119
  35. package/src/controls/color-variables-selection-control.tsx +0 -34
  36. package/src/controls/font-variables-selection-control.tsx +0 -29
@@ -1,70 +1,129 @@
1
1
  import * as React from 'react';
2
- import { Fragment } from 'react';
2
+ import { useState } from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
- import { EditIcon, TextIcon } from '@elementor/icons';
5
- import { Box, Divider, ListItemIcon, ListItemText, MenuList } from '@elementor/ui';
4
+ import { PopoverScrollableContent } from '@elementor/editor-editing-panel';
5
+ import { PopoverHeader, PopoverMenuList, PopoverSearch, type VirtualizedItem } from '@elementor/editor-ui';
6
+ import { ColorFilterIcon, PlusIcon, SettingsIcon, TextIcon } from '@elementor/icons';
7
+ import { Divider, IconButton } from '@elementor/ui';
8
+ import { __ } from '@wordpress/i18n';
6
9
 
7
- import { usePropVariables } from '../hooks/use-prop-variables';
10
+ import { useFilteredVariables } from '../hooks/use-prop-variables';
8
11
  import { fontVariablePropTypeUtil } from '../prop-types/font-variable-prop-type';
9
- import { type VariableKey } from '../types';
10
- import { StyledMenuItem } from './styled-menu-item';
12
+ import { type ExtendedVirtualizedItem } from '../types';
13
+ import { MenuItemContent } from './ui/menu-item-content';
14
+ import { NoSearchResults } from './ui/no-search-results';
15
+ import { NoVariables } from './ui/no-variables';
16
+ import { VariablesStyledMenuList } from './ui/styled-menu-list';
17
+
18
+ const SIZE = 'tiny';
11
19
 
12
20
  type Props = {
13
- onSelect?: () => void;
21
+ closePopover: () => void;
22
+ onAdd?: () => void;
23
+ onEdit?: ( key: string ) => void;
24
+ onSettings?: () => void;
14
25
  };
15
26
 
16
- export const FontVariablesSelection = ( { onSelect }: Props ) => {
27
+ export const FontVariablesSelection = ( { closePopover, onAdd, onEdit, onSettings }: Props ) => {
17
28
  const { value: variable, setValue: setVariable } = useBoundProp( fontVariablePropTypeUtil );
29
+ const [ searchValue, setSearchValue ] = useState( '' );
18
30
 
19
- const variables = usePropVariables( fontVariablePropTypeUtil.key );
31
+ const {
32
+ list: variables,
33
+ hasMatches: hasSearchResults,
34
+ isSourceNotEmpty: hasVariables,
35
+ } = useFilteredVariables( searchValue, fontVariablePropTypeUtil.key );
20
36
 
21
- const handleSetVariable = ( key: VariableKey ) => {
37
+ const handleSetVariable = ( key: string ) => {
22
38
  setVariable( key );
39
+ closePopover();
40
+ };
41
+
42
+ const actions = [];
43
+
44
+ if ( onAdd ) {
45
+ actions.push(
46
+ <IconButton key="add" size={ SIZE } onClick={ onAdd }>
47
+ <PlusIcon fontSize={ SIZE } />
48
+ </IconButton>
49
+ );
50
+ }
23
51
 
24
- onSelect?.();
52
+ if ( onSettings ) {
53
+ actions.push(
54
+ <IconButton key="settings" size={ SIZE } onClick={ onSettings }>
55
+ <SettingsIcon fontSize={ SIZE } />
56
+ </IconButton>
57
+ );
58
+ }
59
+
60
+ const items: ExtendedVirtualizedItem[] = variables.map( ( { value, label, key } ) => ( {
61
+ type: 'item' as const,
62
+ value: key,
63
+ label,
64
+ icon: <TextIcon fontSize={ SIZE } />,
65
+ secondaryText: value,
66
+ onEdit: () => onEdit?.( key ),
67
+ } ) );
68
+
69
+ const handleSearch = ( search: string ) => {
70
+ setSearchValue( search );
71
+ };
72
+
73
+ const handleClearSearch = () => {
74
+ setSearchValue( '' );
25
75
  };
26
76
 
27
77
  return (
28
- <Fragment>
78
+ <>
79
+ <PopoverHeader
80
+ title={ __( 'Variables', 'elementor' ) }
81
+ onClose={ closePopover }
82
+ icon={ <ColorFilterIcon fontSize={ SIZE } /> }
83
+ actions={ actions }
84
+ />
85
+
86
+ { hasVariables && (
87
+ <PopoverSearch
88
+ value={ searchValue }
89
+ onSearch={ handleSearch }
90
+ placeholder={ __( 'Search', 'elementor' ) }
91
+ />
92
+ ) }
93
+
29
94
  <Divider />
30
- <Box sx={ { overflowY: 'auto', height: 260, width: 220 } }>
31
- <MenuList role="listbox" tabIndex={ 0 }>
32
- { variables.map( ( { value, label, key } ) => (
33
- <StyledMenuItem
34
- key={ key }
35
- onClick={ () => handleSetVariable( key ) }
36
- selected={ key === variable }
37
- >
38
- <ListItemIcon>
39
- <TextIcon />
40
- </ListItemIcon>
41
- <ListItemText
42
- primary={ label }
43
- secondary={ value }
44
- primaryTypographyProps={ {
45
- variant: 'body2',
46
- color: 'text.secondary',
47
- style: {
48
- lineHeight: 2,
49
- display: 'inline-block',
50
- overflow: 'hidden',
51
- textOverflow: 'ellipsis',
52
- whiteSpace: 'nowrap',
53
- maxWidth: '81px',
54
- },
55
- } }
56
- secondaryTypographyProps={ {
57
- variant: 'caption',
58
- color: 'text.tertiary',
59
- style: { marginTop: '1px', lineHeight: '1' },
60
- } }
61
- sx={ { display: 'flex', alignItems: 'center', gap: 1 } }
62
- />
63
- <EditIcon color="action" fontSize="inherit" sx={ { mx: 1, opacity: '0' } } />
64
- </StyledMenuItem>
65
- ) ) }
66
- </MenuList>
67
- </Box>
68
- </Fragment>
95
+
96
+ <PopoverScrollableContent>
97
+ { hasVariables && hasSearchResults && (
98
+ <PopoverMenuList
99
+ items={ items }
100
+ onSelect={ handleSetVariable }
101
+ onClose={ () => {} }
102
+ selectedValue={ variable }
103
+ data-testid="font-variables-list"
104
+ menuListTemplate={ VariablesStyledMenuList }
105
+ menuItemContentTemplate={ ( item: VirtualizedItem< 'item', string > ) => (
106
+ <MenuItemContent item={ item } />
107
+ ) }
108
+ />
109
+ ) }
110
+
111
+ { ! hasSearchResults && hasVariables && (
112
+ <NoSearchResults
113
+ searchValue={ searchValue }
114
+ onClear={ handleClearSearch }
115
+ icon={ <TextIcon fontSize="large" /> }
116
+ />
117
+ ) }
118
+
119
+ { ! hasVariables && (
120
+ <NoVariables
121
+ title={ __( 'Create your first font variable', 'elementor' ) }
122
+ icon={ <TextIcon fontSize="large" /> }
123
+ onAdd={ onAdd }
124
+ />
125
+ ) }
126
+ </PopoverScrollableContent>
127
+ </>
69
128
  );
70
129
  };
@@ -2,4 +2,5 @@ import { styled, UnstableColorIndicator } from '@elementor/ui';
2
2
 
3
3
  export const ColorIndicator = styled( UnstableColorIndicator )( ( { theme } ) => ( {
4
4
  borderRadius: `${ theme.shape.borderRadius / 2 }px`,
5
+ marginRight: theme.spacing( 0.25 ),
5
6
  } ) );
@@ -0,0 +1,60 @@
1
+ import * as React from 'react';
2
+ import { EllipsisWithTooltip, type VirtualizedItem } from '@elementor/editor-ui';
3
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
4
+ import { EditIcon } from '@elementor/icons';
5
+ import { Box, IconButton, ListItemIcon, Typography } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ const SIZE = 'tiny';
9
+
10
+ const isVersion330Active = isExperimentActive( 'e_v_3_30' );
11
+
12
+ export const MenuItemContent = < T, V extends string >( { item }: { item: VirtualizedItem< T, V > } ) => {
13
+ const onEdit = item.onEdit as ( ( value: V ) => void ) | undefined;
14
+
15
+ return (
16
+ <>
17
+ <ListItemIcon>{ item.icon }</ListItemIcon>
18
+ <Box
19
+ sx={ {
20
+ flex: 1,
21
+ minWidth: 0,
22
+ display: 'flex',
23
+ alignItems: 'center',
24
+ gap: 1,
25
+ } }
26
+ >
27
+ <EllipsisWithTooltip
28
+ title={ item.label || item.value }
29
+ as={ Typography }
30
+ variant={ isVersion330Active ? 'caption' : 'body2' }
31
+ color={ isVersion330Active ? 'text.primary' : 'text.secondary' }
32
+ sx={ { marginTop: '1px', lineHeight: '2' } }
33
+ maxWidth="50%"
34
+ />
35
+ { item.secondaryText && (
36
+ <EllipsisWithTooltip
37
+ title={ item.secondaryText }
38
+ as={ Typography }
39
+ variant="caption"
40
+ color="text.tertiary"
41
+ sx={ { marginTop: '1px', lineHeight: '1' } }
42
+ maxWidth="50%"
43
+ />
44
+ ) }
45
+ </Box>
46
+ { !! onEdit && (
47
+ <IconButton
48
+ sx={ { mx: 1, opacity: '0' } }
49
+ onClick={ ( e: React.MouseEvent< HTMLButtonElement > ) => {
50
+ e.stopPropagation();
51
+ onEdit( item.value );
52
+ } }
53
+ aria-label={ __( 'Edit', 'elementor' ) }
54
+ >
55
+ <EditIcon color="action" fontSize={ SIZE } />
56
+ </IconButton>
57
+ ) }
58
+ </>
59
+ );
60
+ };
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import { Link, Stack, Typography } from '@elementor/ui';
3
+ import { __ } from '@wordpress/i18n';
4
+
5
+ type Props = {
6
+ searchValue: string;
7
+ onClear?: () => void;
8
+ icon?: React.ReactNode;
9
+ };
10
+
11
+ export const NoSearchResults = ( { searchValue, onClear, icon }: Props ) => {
12
+ return (
13
+ <Stack
14
+ gap={ 1 }
15
+ alignItems="center"
16
+ justifyContent="center"
17
+ height="100%"
18
+ p={ 2.5 }
19
+ color="text.secondary"
20
+ sx={ { pb: 3.5 } }
21
+ >
22
+ { icon }
23
+ <Typography align="center" variant="subtitle2">
24
+ { __( 'Sorry, nothing matched', 'elementor' ) }
25
+ <br />
26
+ &ldquo;{ searchValue }&rdquo;.
27
+ </Typography>
28
+ <Typography align="center" variant="caption" sx={ { display: 'flex', flexDirection: 'column' } }>
29
+ { __( 'Try something else.', 'elementor' ) }
30
+ <Link color="text.secondary" variant="caption" component="button" onClick={ onClear }>
31
+ { __( 'Clear & try again', 'elementor' ) }
32
+ </Link>
33
+ </Typography>
34
+ </Stack>
35
+ );
36
+ };
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import { Button, Stack, Typography } from '@elementor/ui';
3
+ import { __ } from '@wordpress/i18n';
4
+
5
+ type Props = {
6
+ icon?: React.ReactNode;
7
+ title?: string;
8
+ onAdd?: () => void;
9
+ };
10
+
11
+ export const NoVariables = ( { icon, title, onAdd }: Props ) => (
12
+ <Stack
13
+ gap={ 1 }
14
+ alignItems="center"
15
+ justifyContent="center"
16
+ height="100%"
17
+ color="text.secondary"
18
+ sx={ { p: 2.5, pb: 5.5 } }
19
+ >
20
+ { icon }
21
+
22
+ <Typography align="center" variant="subtitle2">
23
+ { title }
24
+ </Typography>
25
+
26
+ <Typography align="center" variant="caption" maxWidth="180px">
27
+ { __( 'Variables are saved attributes that you can apply anywhere on your site.', 'elementor' ) }
28
+ </Typography>
29
+
30
+ { onAdd && (
31
+ <Button variant="outlined" color="secondary" size="small" onClick={ onAdd }>
32
+ { __( 'Create a variable', 'elementor' ) }
33
+ </Button>
34
+ ) }
35
+ </Stack>
36
+ );
@@ -0,0 +1,31 @@
1
+ import { MenuList, styled } from '@elementor/ui';
2
+
3
+ export const VariablesStyledMenuList = styled( MenuList )( ( { theme } ) => ( {
4
+ '& > li': {
5
+ height: 32,
6
+ width: '100%',
7
+ display: 'flex',
8
+ alignItems: 'center',
9
+ },
10
+ '& > [role="option"]': {
11
+ ...theme.typography.caption,
12
+ lineHeight: 'inherit',
13
+ padding: theme.spacing( 0.5, 1, 0.5, 2 ),
14
+ '&:hover, &:focus': {
15
+ backgroundColor: theme.palette.action.hover,
16
+ },
17
+ '&[aria-selected="true"]': {
18
+ backgroundColor: theme.palette.action.selected,
19
+ },
20
+ cursor: 'pointer',
21
+ textOverflow: 'ellipsis',
22
+ position: 'absolute',
23
+ top: 0,
24
+ left: 0,
25
+ '&:hover .MuiIconButton-root, .MuiIconButton-root:focus': {
26
+ opacity: 1,
27
+ },
28
+ },
29
+ width: '100%',
30
+ position: 'relative',
31
+ } ) );
@@ -0,0 +1,43 @@
1
+ import * as React from 'react';
2
+ import { DetachIcon } from '@elementor/icons';
3
+ import { Box, IconButton, Stack, Typography, UnstableTag as Tag, type UnstableTagProps } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ export const SIZE = 'tiny';
7
+
8
+ interface VariableTagProps extends UnstableTagProps {
9
+ onUnlink?: () => void;
10
+ }
11
+
12
+ export const VariableTag = ( { startIcon, label, onUnlink, ...props }: VariableTagProps ) => {
13
+ const actions = [];
14
+
15
+ if ( onUnlink ) {
16
+ actions.push(
17
+ <IconButton key="unlink" size={ SIZE } onClick={ onUnlink } aria-label={ __( 'Unlink', 'elementor' ) }>
18
+ <DetachIcon fontSize={ SIZE } />
19
+ </IconButton>
20
+ );
21
+ }
22
+
23
+ return (
24
+ <Tag
25
+ fullWidth
26
+ showActionsOnHover
27
+ startIcon={
28
+ <Stack gap={ 0.5 } direction="row" alignItems="center">
29
+ { startIcon }
30
+ </Stack>
31
+ }
32
+ label={
33
+ <Box sx={ { display: 'inline-grid', minWidth: 0 } }>
34
+ <Typography sx={ { lineHeight: 1.34 } } variant="caption" noWrap>
35
+ { label }
36
+ </Typography>
37
+ </Box>
38
+ }
39
+ actions={ actions }
40
+ { ...props }
41
+ />
42
+ );
43
+ };
@@ -0,0 +1,7 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export const PopoverContentRefContext = createContext< React.RefObject< HTMLDivElement > | null >( null );
4
+
5
+ export const usePopoverContentRef = () => {
6
+ return useContext( PopoverContentRefContext );
7
+ };
@@ -0,0 +1,142 @@
1
+ import * as React from 'react';
2
+ import { useRef, useState } from 'react';
3
+ import { Box } from '@elementor/ui';
4
+
5
+ import { colorVariablePropTypeUtil } from '../prop-types/color-variable-prop-type';
6
+ import { fontVariablePropTypeUtil } from '../prop-types/font-variable-prop-type';
7
+ import { type Variable } from '../types';
8
+ import { ColorVariableCreation } from './color-variable-creation';
9
+ import { ColorVariableEdit } from './color-variable-edit';
10
+ import { ColorVariablesSelection } from './color-variables-selection';
11
+ import { FontVariableCreation } from './font-variable-creation';
12
+ import { FontVariableEdit } from './font-variable-edit';
13
+ import { FontVariablesSelection } from './font-variables-selection';
14
+ import { PopoverContentRefContext } from './variable-selection-popover.context';
15
+
16
+ const VIEW_LIST = 'list';
17
+ const VIEW_ADD = 'add';
18
+ const VIEW_EDIT = 'edit';
19
+
20
+ type View = typeof VIEW_LIST | typeof VIEW_ADD | typeof VIEW_EDIT;
21
+
22
+ type Props = {
23
+ closePopover: () => void;
24
+ propTypeKey: string;
25
+ selectedVariable?: Variable;
26
+ };
27
+
28
+ export const VariableSelectionPopover = ( { closePopover, propTypeKey, selectedVariable }: Props ) => {
29
+ const [ currentView, setCurrentView ] = useState< View >( VIEW_LIST );
30
+ const editIdRef = useRef< string >( '' );
31
+ const anchorRef = useRef< HTMLDivElement >( null );
32
+
33
+ return (
34
+ <PopoverContentRefContext.Provider value={ anchorRef }>
35
+ <Box ref={ anchorRef }>
36
+ { renderStage( {
37
+ propTypeKey,
38
+ currentView,
39
+ selectedVariable,
40
+ editIdRef,
41
+ setCurrentView,
42
+ closePopover,
43
+ } ) }
44
+ </Box>
45
+ </PopoverContentRefContext.Provider>
46
+ );
47
+ };
48
+
49
+ type StageProps = {
50
+ propTypeKey: string;
51
+ currentView: View;
52
+ selectedVariable?: Variable;
53
+ editIdRef: React.MutableRefObject< string >;
54
+ setCurrentView: ( stage: View ) => void;
55
+ closePopover: () => void;
56
+ };
57
+
58
+ function renderStage( props: StageProps ): React.ReactNode {
59
+ const handleSubmitOnEdit = () => {
60
+ if ( props?.selectedVariable?.key === props.editIdRef.current ) {
61
+ props.closePopover();
62
+ } else {
63
+ props.setCurrentView( VIEW_LIST );
64
+ }
65
+ };
66
+
67
+ if ( fontVariablePropTypeUtil.key === props.propTypeKey ) {
68
+ if ( VIEW_LIST === props.currentView ) {
69
+ return (
70
+ <FontVariablesSelection
71
+ closePopover={ props.closePopover }
72
+ onAdd={ () => {
73
+ props.setCurrentView( VIEW_ADD );
74
+ } }
75
+ onEdit={ ( key ) => {
76
+ props.editIdRef.current = key;
77
+ props.setCurrentView( VIEW_EDIT );
78
+ } }
79
+ />
80
+ );
81
+ }
82
+
83
+ if ( VIEW_ADD === props.currentView ) {
84
+ return (
85
+ <FontVariableCreation
86
+ onGoBack={ () => props.setCurrentView( VIEW_LIST ) }
87
+ onClose={ props.closePopover }
88
+ />
89
+ );
90
+ }
91
+
92
+ if ( VIEW_EDIT === props.currentView ) {
93
+ return (
94
+ <FontVariableEdit
95
+ editId={ props.editIdRef.current ?? '' }
96
+ onGoBack={ () => props.setCurrentView( VIEW_LIST ) }
97
+ onClose={ props.closePopover }
98
+ onSubmit={ handleSubmitOnEdit }
99
+ />
100
+ );
101
+ }
102
+ }
103
+
104
+ if ( colorVariablePropTypeUtil.key === props.propTypeKey ) {
105
+ if ( VIEW_LIST === props.currentView ) {
106
+ return (
107
+ <ColorVariablesSelection
108
+ closePopover={ props.closePopover }
109
+ onAdd={ () => {
110
+ props.setCurrentView( VIEW_ADD );
111
+ } }
112
+ onEdit={ ( key ) => {
113
+ props.editIdRef.current = key;
114
+ props.setCurrentView( VIEW_EDIT );
115
+ } }
116
+ />
117
+ );
118
+ }
119
+
120
+ if ( VIEW_ADD === props.currentView ) {
121
+ return (
122
+ <ColorVariableCreation
123
+ onGoBack={ () => props.setCurrentView( VIEW_LIST ) }
124
+ onClose={ props.closePopover }
125
+ />
126
+ );
127
+ }
128
+
129
+ if ( VIEW_EDIT === props.currentView ) {
130
+ return (
131
+ <ColorVariableEdit
132
+ editId={ props.editIdRef.current ?? '' }
133
+ onGoBack={ () => props.setCurrentView( VIEW_LIST ) }
134
+ onClose={ props.closePopover }
135
+ onSubmit={ handleSubmitOnEdit }
136
+ />
137
+ );
138
+ }
139
+ }
140
+
141
+ return null;
142
+ }
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import { type BackgroundColorOverlayPropValue, type BoxShadowPropValue, type PropValue } from '@elementor/editor-props';
3
+
4
+ import { useVariable } from '../hooks/use-prop-variables';
5
+ import { ColorIndicator } from './ui/color-indicator';
6
+
7
+ const useColorVariable = ( value: BackgroundColorOverlayPropValue | BoxShadowPropValue ) => {
8
+ const variableId = value?.value?.color?.value;
9
+
10
+ return useVariable( variableId || '' );
11
+ };
12
+
13
+ export const BackgroundRepeaterColorIndicator = ( { value }: { value: PropValue } ) => {
14
+ const colorVariable = useColorVariable( value as BackgroundColorOverlayPropValue );
15
+
16
+ return <ColorIndicator component="span" size="inherit" value={ colorVariable?.value } />;
17
+ };
18
+
19
+ export const BackgroundRepeaterLabel = ( { value }: { value: PropValue } ) => {
20
+ const colorVariable = useColorVariable( value as BackgroundColorOverlayPropValue );
21
+
22
+ return <span>{ colorVariable?.label }</span>;
23
+ };
24
+
25
+ export const BoxShadowRepeaterColorIndicator = ( { value }: { value: PropValue } ) => {
26
+ const colorVariable = useColorVariable( value as BoxShadowPropValue );
27
+
28
+ return <ColorIndicator component="span" size="inherit" value={ colorVariable?.value } />;
29
+ };
@@ -0,0 +1,66 @@
1
+ import * as React from 'react';
2
+ import { useId, useRef } from 'react';
3
+ import { useBoundProp } from '@elementor/editor-controls';
4
+ import { colorPropTypeUtil } from '@elementor/editor-props';
5
+ import { ColorFilterIcon } from '@elementor/icons';
6
+ import { bindPopover, bindTrigger, Box, Popover, usePopupState } from '@elementor/ui';
7
+
8
+ import { ColorIndicator } from '../components/ui/color-indicator';
9
+ import { SIZE, VariableTag } from '../components/ui/variable-tag';
10
+ import { VariableSelectionPopover } from '../components/variable-selection-popover';
11
+ import { useVariable } from '../hooks/use-prop-variables';
12
+ import { colorVariablePropTypeUtil } from '../prop-types/color-variable-prop-type';
13
+
14
+ export const ColorVariableControl = () => {
15
+ const { setValue: setColor } = useBoundProp();
16
+ const { value: variableValue } = useBoundProp( colorVariablePropTypeUtil );
17
+
18
+ const anchorRef = useRef< HTMLDivElement >( null );
19
+
20
+ const popupId = useId();
21
+ const popupState = usePopupState( {
22
+ variant: 'popover',
23
+ popupId: `elementor-variables-list-${ popupId }`,
24
+ } );
25
+
26
+ const selectedVariable = useVariable( variableValue );
27
+ if ( ! selectedVariable ) {
28
+ throw new Error( `Global color variable ${ variableValue } not found` );
29
+ }
30
+
31
+ const unlinkVariable = () => {
32
+ setColor( colorPropTypeUtil.create( selectedVariable.value ) );
33
+ };
34
+
35
+ return (
36
+ <Box ref={ anchorRef }>
37
+ <VariableTag
38
+ label={ selectedVariable.label }
39
+ startIcon={
40
+ <>
41
+ <ColorFilterIcon fontSize={ SIZE } />
42
+ <ColorIndicator size="inherit" value={ selectedVariable.value } component="span" />
43
+ </>
44
+ }
45
+ onUnlink={ unlinkVariable }
46
+ { ...bindTrigger( popupState ) }
47
+ />
48
+ <Popover
49
+ disableScrollLock
50
+ anchorEl={ anchorRef.current }
51
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
52
+ transformOrigin={ { vertical: 'top', horizontal: 'right' } }
53
+ PaperProps={ {
54
+ sx: { my: 1 },
55
+ } }
56
+ { ...bindPopover( popupState ) }
57
+ >
58
+ <VariableSelectionPopover
59
+ selectedVariable={ selectedVariable }
60
+ closePopover={ popupState.close }
61
+ propTypeKey={ colorVariablePropTypeUtil.key }
62
+ />
63
+ </Popover>
64
+ </Box>
65
+ );
66
+ };