@elementor/editor-variables 0.6.0 → 0.8.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.
@@ -0,0 +1,122 @@
1
+ import * as React from 'react';
2
+ import { useRef, useState } from 'react';
3
+ import { useBoundProp } from '@elementor/editor-controls';
4
+ import { BrushIcon } from '@elementor/icons';
5
+ import {
6
+ bindPopover,
7
+ Box,
8
+ Button,
9
+ CardActions,
10
+ CloseButton,
11
+ Divider,
12
+ FormLabel,
13
+ Grid,
14
+ Popover,
15
+ type PopupState,
16
+ Stack,
17
+ TextField,
18
+ Typography,
19
+ UnstableColorField,
20
+ } from '@elementor/ui';
21
+ import { __ } from '@wordpress/i18n';
22
+
23
+ import { createVariable } from '../hooks/use-prop-variables';
24
+ import { colorVariablePropTypeUtil } from '../prop-types/color-variable-prop-type';
25
+
26
+ export const ColorVariableCreation = ( { popupState }: { popupState: PopupState } ) => {
27
+ const { setValue: setVariable } = useBoundProp( colorVariablePropTypeUtil );
28
+
29
+ const [ color, setColor ] = useState( '' );
30
+ const [ label, setLabel ] = useState( '' );
31
+
32
+ const anchorRef = useRef< HTMLDivElement >( null );
33
+
34
+ const resetFields = () => {
35
+ setColor( '' );
36
+ setLabel( '' );
37
+ };
38
+
39
+ const closePopover = () => {
40
+ resetFields();
41
+ popupState.close();
42
+ };
43
+
44
+ const handleCreate = () => {
45
+ const key = createVariable( colorVariablePropTypeUtil.key, { label, value: color } );
46
+ setVariable( key );
47
+ closePopover();
48
+ };
49
+
50
+ const isInValidForm = () => {
51
+ return ! color?.trim() || ! label?.trim();
52
+ };
53
+
54
+ return (
55
+ <Box ref={ anchorRef }>
56
+ <Popover
57
+ { ...bindPopover( popupState ) }
58
+ anchorEl={ anchorRef.current }
59
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
60
+ transformOrigin={ { vertical: 'top', horizontal: 'right' } }
61
+ >
62
+ <Stack direction="row" alignItems="center" pl={ 1.5 } pr={ 0.5 } py={ 1.5 }>
63
+ <BrushIcon fontSize="tiny" sx={ { mr: 0.5 } } />
64
+ <Typography variant="subtitle2">{ __( 'Create variable', 'elementor' ) }</Typography>
65
+ <CloseButton
66
+ slotProps={ { icon: { fontSize: 'small' } } }
67
+ sx={ { ml: 'auto' } }
68
+ onClick={ closePopover }
69
+ />
70
+ </Stack>
71
+
72
+ <Divider />
73
+
74
+ <Stack p={ 1.5 } gap={ 1.5 }>
75
+ <Grid container gap={ 0.75 } alignItems="center">
76
+ <Grid item xs={ 12 }>
77
+ <FormLabel size="small">{ __( 'Name', 'elementor' ) }</FormLabel>
78
+ </Grid>
79
+ <Grid item xs={ 12 }>
80
+ <TextField
81
+ size="tiny"
82
+ fullWidth
83
+ value={ label }
84
+ onChange={ ( e: React.ChangeEvent< HTMLInputElement > ) => setLabel( e.target.value ) }
85
+ />
86
+ </Grid>
87
+ </Grid>
88
+
89
+ <Grid container gap={ 0.75 } alignItems="center">
90
+ <Grid item xs={ 12 }>
91
+ <FormLabel size="small">{ __( 'Value', 'elementor' ) }</FormLabel>
92
+ </Grid>
93
+ <Grid item xs={ 12 }>
94
+ <UnstableColorField
95
+ size="tiny"
96
+ fullWidth
97
+ value={ color }
98
+ onChange={ setColor }
99
+ slotProps={ {
100
+ colorPicker: {
101
+ anchorEl: anchorRef.current,
102
+ anchorOrigin: { vertical: 'top', horizontal: 'right' },
103
+ transformOrigin: { vertical: 'top', horizontal: -10 },
104
+ },
105
+ } }
106
+ />
107
+ </Grid>
108
+ </Grid>
109
+ </Stack>
110
+
111
+ <CardActions>
112
+ <Button size="small" onClick={ closePopover } color="secondary" variant="text">
113
+ { __( 'Cancel', 'elementor' ) }
114
+ </Button>
115
+ <Button size="small" variant="contained" disabled={ isInValidForm() } onClick={ handleCreate }>
116
+ { __( 'Create', 'elementor' ) }
117
+ </Button>
118
+ </CardActions>
119
+ </Popover>
120
+ </Box>
121
+ );
122
+ };
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import { useId } from 'react';
3
- import { ColorFilterIcon, DetachIcon } from '@elementor/icons';
2
+ import { useId, useRef } from 'react';
3
+ import { ColorFilterIcon, DetachIcon, PlusIcon } from '@elementor/icons';
4
4
  import {
5
5
  bindPopover,
6
6
  bindTrigger,
@@ -16,6 +16,7 @@ import {
16
16
  import { __ } from '@wordpress/i18n';
17
17
 
18
18
  import { type Variable } from '../types';
19
+ import { ColorVariableCreation } from './color-variable-creation';
19
20
 
20
21
  type Props = {
21
22
  selectedVariable: Variable;
@@ -32,13 +33,20 @@ export const VariablesSelectionPopover = ( {
32
33
  }: Props ) => {
33
34
  const id = useId();
34
35
  const popupState = usePopupState( { variant: 'popover', popupId: `elementor-variables-action-${ id }` } );
36
+ const creationPopupState = usePopupState( { variant: 'popover', popupId: `elementor-variables-creation-${ id }` } );
35
37
 
36
38
  const closePopover = () => popupState.close();
37
39
 
40
+ const handleCreateButtonClick = ( event: React.MouseEvent ) => {
41
+ closePopover();
42
+ bindTrigger( creationPopupState ).onClick( event );
43
+ };
44
+
45
+ const anchorRef = useRef< HTMLDivElement >( null );
38
46
  const { label } = selectedVariable;
39
47
 
40
48
  return (
41
- <Box>
49
+ <Box ref={ anchorRef }>
42
50
  <Tag
43
51
  fullWidth
44
52
  showActionsOnHover
@@ -57,28 +65,36 @@ export const VariablesSelectionPopover = ( {
57
65
  </Box>
58
66
  }
59
67
  actions={
60
- <IconButton size={ 'tiny' } onClick={ unlinkVariable } aria-label={ __( 'Unlink', 'elementor' ) }>
61
- <DetachIcon fontSize={ 'tiny' } />
68
+ <IconButton size="tiny" onClick={ unlinkVariable } aria-label={ __( 'Unlink', 'elementor' ) }>
69
+ <DetachIcon fontSize="tiny" />
62
70
  </IconButton>
63
71
  }
64
72
  />
65
73
  <Popover
66
- disableScrollLock
67
- anchorOrigin={ { vertical: 'bottom', horizontal: 'center' } }
68
- transformOrigin={ { vertical: 'top', horizontal: 'center' } }
69
74
  { ...bindPopover( popupState ) }
75
+ disableScrollLock
76
+ anchorEl={ anchorRef.current }
77
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'right' } }
78
+ transformOrigin={ { vertical: 'top', horizontal: 'right' } }
70
79
  >
71
80
  <Stack direction="row" alignItems="center" pl={ 1.5 } pr={ 0.5 } py={ 1.5 }>
72
- <ColorFilterIcon fontSize={ 'tiny' } sx={ { mr: 0.5 } } />
81
+ <ColorFilterIcon fontSize="tiny" sx={ { mr: 0.5 } } />
73
82
  <Typography variant="subtitle2">{ __( 'Variables', 'elementor' ) }</Typography>
74
- <CloseButton
75
- slotProps={ { icon: { fontSize: 'tiny' } } }
76
- sx={ { ml: 'auto' } }
77
- onClick={ closePopover }
78
- />
83
+ <Stack direction="row" sx={ { ml: 'auto' } }>
84
+ <IconButton
85
+ { ...bindTrigger( creationPopupState ) }
86
+ size="tiny"
87
+ onClick={ handleCreateButtonClick }
88
+ >
89
+ <PlusIcon fontSize="tiny" />
90
+ </IconButton>
91
+ <CloseButton slotProps={ { icon: { fontSize: 'tiny' } } } onClick={ closePopover } />
92
+ </Stack>
79
93
  </Stack>
80
- { children( { closePopover } ) }
94
+ { children?.( { closePopover } ) }
81
95
  </Popover>
96
+
97
+ <ColorVariableCreation popupState={ creationPopupState } />
82
98
  </Box>
83
99
  );
84
100
  };
@@ -1,6 +1,12 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
- type Variables = Record< string, { value: string; label: string } >;
3
+ import { type Variable } from '../types';
4
+
5
+ type VariableData = {
6
+ value: string;
7
+ label: string;
8
+ };
9
+ type Variables = Record< string, VariableData >;
4
10
 
5
11
  type VariablesGroup = Record< string, Variables >;
6
12
 
@@ -9,7 +15,7 @@ export const usePropVariables = ( propTypeKey: string ) => {
9
15
  };
10
16
 
11
17
  export const useVariable = ( propTypeKey: string, key: string ) => {
12
- if ( ! variables[ propTypeKey ][ key ] ) {
18
+ if ( ! variables[ propTypeKey ]?.[ key ] ) {
13
19
  return null;
14
20
  }
15
21
 
@@ -20,12 +26,32 @@ export const useVariable = ( propTypeKey: string, key: string ) => {
20
26
  };
21
27
 
22
28
  const normalizeVariables = ( propTypeKey: string ) => {
23
- return Object.entries( variables[ propTypeKey ] ).map( ( [ key, { label, value } ] ) => ( {
29
+ return Object.entries( variables[ propTypeKey ] || {} ).map( ( [ key, { label, value } ] ) => ( {
24
30
  key,
25
31
  label,
26
32
  value,
27
33
  } ) );
28
34
  };
29
35
 
36
+ export const createVariable = ( propTypeKey: string, variable: VariableData ) => {
37
+ const id = generateId( 'e-gv', Object.keys( variables[ propTypeKey ] ).length );
38
+
39
+ const newVariable: Variable = {
40
+ value: variable.value,
41
+ label: variable.label,
42
+ key: propTypeKey,
43
+ };
44
+
45
+ variables[ propTypeKey ][ id ] = newVariable || {};
46
+
47
+ return id;
48
+ };
49
+
30
50
  // @ts-expect-error the temporary solution to get the list of variables from the server
31
- const variables: VariablesGroup = window?.ElementorV4Variables;
51
+ const variables: VariablesGroup = window?.ElementorV4Variables || {};
52
+
53
+ const generateId = ( prefix: string, variablesCount: number ) => {
54
+ const randomHex = Math.random().toString( 16 ).slice( 2, 9 );
55
+
56
+ return `${ prefix }${ randomHex }${ variablesCount }`;
57
+ };