@elementor/editor-variables 0.15.0 → 0.18.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 +88 -0
  2. package/dist/index.js +894 -486
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +892 -511
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +9 -9
  7. package/src/components/color-variable-creation.tsx +37 -58
  8. package/src/components/color-variable-edit.tsx +110 -86
  9. package/src/components/color-variables-selection.tsx +32 -34
  10. package/src/components/fields/color-field.tsx +54 -0
  11. package/src/components/fields/font-field.tsx +85 -0
  12. package/src/components/fields/label-field.tsx +54 -0
  13. package/src/components/font-variable-creation.tsx +39 -78
  14. package/src/components/font-variable-edit.tsx +108 -114
  15. package/src/components/font-variables-selection.tsx +32 -34
  16. package/src/components/ui/delete-confirmation-dialog.tsx +52 -0
  17. package/src/components/ui/deleted-variable-alert.tsx +47 -0
  18. package/src/components/ui/menu-item-content.tsx +2 -5
  19. package/src/components/ui/tags/assigned-tag.tsx +45 -0
  20. package/src/components/ui/tags/deleted-tag.tsx +37 -0
  21. package/src/components/ui/variable/assigned-variable.tsx +70 -0
  22. package/src/components/ui/variable/deleted-variable.tsx +76 -0
  23. package/src/controls/color-variable-control.tsx +21 -48
  24. package/src/controls/font-variable-control.tsx +20 -43
  25. package/src/create-style-variables-repository.ts +44 -5
  26. package/src/hooks/use-prop-variables.ts +6 -0
  27. package/src/init-color-variables.ts +3 -48
  28. package/src/renderers/style-variables-renderer.tsx +10 -4
  29. package/src/repeater-injections.ts +35 -0
  30. package/src/service.ts +23 -2
  31. package/src/sync/enqueue-font.ts +7 -0
  32. package/src/sync/types.ts +5 -0
  33. package/src/transformers/variable-transformer.ts +21 -3
  34. package/src/types.ts +1 -1
  35. package/src/utils/validations.ts +42 -0
  36. package/src/components/ui/variable-tag.tsx +0 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-variables",
3
- "version": "0.15.0",
3
+ "version": "0.18.0",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -39,18 +39,18 @@
39
39
  "dev": "tsup --config=../../tsup.dev.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@elementor/editor": "0.20.1",
43
- "@elementor/editor-editing-panel": "1.47.0",
44
- "@elementor/editor-canvas": "0.25.0",
45
- "@elementor/editor-props": "0.15.0",
42
+ "@elementor/editor": "0.21.1",
43
+ "@elementor/editor-editing-panel": "1.50.0",
44
+ "@elementor/editor-canvas": "0.28.0",
45
+ "@elementor/editor-props": "0.18.0",
46
46
  "@elementor/schema": "0.1.2",
47
- "@elementor/editor-controls": "1.2.0",
48
- "@elementor/icons": "1.44.0",
47
+ "@elementor/editor-controls": "1.5.0",
48
+ "@elementor/icons": "1.46.0",
49
49
  "@wordpress/i18n": "^5.13.0",
50
- "@elementor/ui": "1.35.5",
50
+ "@elementor/ui": "1.36.0",
51
51
  "@elementor/editor-v1-adapters": "0.12.1",
52
52
  "@elementor/http-client": "0.3.0",
53
- "@elementor/editor-ui": "0.13.0"
53
+ "@elementor/editor-ui": "0.14.2"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "react": "^18.3.1",
@@ -1,24 +1,16 @@
1
1
  import * as React from 'react';
2
- import { useRef, useState } from 'react';
2
+ import { useState } from 'react';
3
3
  import { PopoverContent, useBoundProp } from '@elementor/editor-controls';
4
- import { PopoverScrollableContent } from '@elementor/editor-editing-panel';
4
+ import { PopoverBody } from '@elementor/editor-editing-panel';
5
5
  import { PopoverHeader } from '@elementor/editor-ui';
6
6
  import { ArrowLeftIcon, BrushIcon } from '@elementor/icons';
7
- import {
8
- Button,
9
- CardActions,
10
- Divider,
11
- FormLabel,
12
- Grid,
13
- IconButton,
14
- TextField,
15
- UnstableColorField,
16
- } from '@elementor/ui';
7
+ import { Button, CardActions, Divider, FormHelperText, IconButton } from '@elementor/ui';
17
8
  import { __ } from '@wordpress/i18n';
18
9
 
19
10
  import { createVariable } from '../hooks/use-prop-variables';
20
11
  import { colorVariablePropTypeUtil } from '../prop-types/color-variable-prop-type';
21
- import { usePopoverContentRef } from './variable-selection-popover.context';
12
+ import { ColorField } from './fields/color-field';
13
+ import { LabelField } from './fields/label-field';
22
14
 
23
15
  const SIZE = 'tiny';
24
16
 
@@ -32,13 +24,12 @@ export const ColorVariableCreation = ( { onGoBack, onClose }: Props ) => {
32
24
 
33
25
  const [ color, setColor ] = useState( '' );
34
26
  const [ label, setLabel ] = useState( '' );
35
-
36
- const defaultRef = useRef< HTMLDivElement >( null );
37
- const anchorRef = usePopoverContentRef() ?? defaultRef;
27
+ const [ errorMessage, setErrorMessage ] = useState( '' );
38
28
 
39
29
  const resetFields = () => {
40
30
  setColor( '' );
41
31
  setLabel( '' );
32
+ setErrorMessage( '' );
42
33
  };
43
34
 
44
35
  const closePopover = () => {
@@ -51,18 +42,24 @@ export const ColorVariableCreation = ( { onGoBack, onClose }: Props ) => {
51
42
  value: color,
52
43
  label,
53
44
  type: colorVariablePropTypeUtil.key,
54
- } ).then( ( key ) => {
55
- setVariable( key );
56
- closePopover();
57
- } );
45
+ } )
46
+ .then( ( key ) => {
47
+ setVariable( key );
48
+ closePopover();
49
+ } )
50
+ .catch( ( error ) => {
51
+ setErrorMessage( error.message );
52
+ } );
58
53
  };
59
54
 
60
- const isFormInvalid = () => {
61
- return ! color?.trim() || ! label?.trim();
55
+ const hasEmptyValue = () => {
56
+ return '' === color.trim() || '' === label.trim();
62
57
  };
63
58
 
59
+ const isSubmitDisabled = hasEmptyValue();
60
+
64
61
  return (
65
- <PopoverScrollableContent height="auto">
62
+ <PopoverBody height="auto">
66
63
  <PopoverHeader
67
64
  icon={
68
65
  <>
@@ -81,47 +78,29 @@ export const ColorVariableCreation = ( { onGoBack, onClose }: Props ) => {
81
78
  <Divider />
82
79
 
83
80
  <PopoverContent p={ 2 }>
84
- <Grid container gap={ 0.75 } alignItems="center">
85
- <Grid item xs={ 12 }>
86
- <FormLabel size="tiny">{ __( 'Name', 'elementor' ) }</FormLabel>
87
- </Grid>
88
- <Grid item xs={ 12 }>
89
- <TextField
90
- size="tiny"
91
- fullWidth
92
- value={ label }
93
- onChange={ ( e: React.ChangeEvent< HTMLInputElement > ) => setLabel( e.target.value ) }
94
- />
95
- </Grid>
96
- </Grid>
81
+ <LabelField
82
+ value={ label }
83
+ onChange={ ( value ) => {
84
+ setLabel( value );
85
+ setErrorMessage( '' );
86
+ } }
87
+ />
88
+ <ColorField
89
+ value={ color }
90
+ onChange={ ( value ) => {
91
+ setColor( value );
92
+ setErrorMessage( '' );
93
+ } }
94
+ />
97
95
 
98
- <Grid container gap={ 0.75 } alignItems="center">
99
- <Grid item xs={ 12 }>
100
- <FormLabel size="tiny">{ __( 'Value', 'elementor' ) }</FormLabel>
101
- </Grid>
102
- <Grid item xs={ 12 }>
103
- <UnstableColorField
104
- size="tiny"
105
- fullWidth
106
- value={ color }
107
- onChange={ setColor }
108
- slotProps={ {
109
- colorPicker: {
110
- anchorEl: anchorRef.current,
111
- anchorOrigin: { vertical: 'top', horizontal: 'right' },
112
- transformOrigin: { vertical: 'top', horizontal: -10 },
113
- },
114
- } }
115
- />
116
- </Grid>
117
- </Grid>
96
+ { errorMessage && <FormHelperText error>{ errorMessage }</FormHelperText> }
118
97
  </PopoverContent>
119
98
 
120
99
  <CardActions sx={ { pt: 0.5, pb: 1 } }>
121
- <Button size="small" variant="contained" disabled={ isFormInvalid() } onClick={ handleCreate }>
100
+ <Button size="small" variant="contained" disabled={ isSubmitDisabled } onClick={ handleCreate }>
122
101
  { __( 'Create', 'elementor' ) }
123
102
  </Button>
124
103
  </CardActions>
125
- </PopoverScrollableContent>
104
+ </PopoverBody>
126
105
  );
127
106
  };
@@ -1,22 +1,17 @@
1
1
  import * as React from 'react';
2
- import { useRef, useState } from 'react';
3
- import { PopoverContent } from '@elementor/editor-controls';
4
- import { PopoverHeader, PopoverScrollableContent } from '@elementor/editor-ui';
2
+ import { useState } from 'react';
3
+ import { PopoverContent, useBoundProp } from '@elementor/editor-controls';
4
+ import { PopoverBody } from '@elementor/editor-editing-panel';
5
+ import { PopoverHeader } from '@elementor/editor-ui';
5
6
  import { ArrowLeftIcon, BrushIcon, TrashIcon } from '@elementor/icons';
6
- import {
7
- Button,
8
- CardActions,
9
- Divider,
10
- FormLabel,
11
- Grid,
12
- IconButton,
13
- TextField,
14
- UnstableColorField,
15
- } from '@elementor/ui';
7
+ import { Button, CardActions, Divider, FormHelperText, IconButton } from '@elementor/ui';
16
8
  import { __ } from '@wordpress/i18n';
17
9
 
18
10
  import { deleteVariable, updateVariable, useVariable } from '../hooks/use-prop-variables';
19
- import { usePopoverContentRef } from './variable-selection-popover.context';
11
+ import { colorVariablePropTypeUtil } from '../prop-types/color-variable-prop-type';
12
+ import { ColorField } from './fields/color-field';
13
+ import { LabelField } from './fields/label-field';
14
+ import { DeleteConfirmationDialog } from './ui/delete-confirmation-dialog';
20
15
 
21
16
  const SIZE = 'tiny';
22
17
 
@@ -28,14 +23,15 @@ type Props = {
28
23
  };
29
24
 
30
25
  export const ColorVariableEdit = ( { onClose, onGoBack, onSubmit, editId }: Props ) => {
26
+ const { setValue: notifyBoundPropChange, value: assignedValue } = useBoundProp( colorVariablePropTypeUtil );
27
+ const [ deleteConfirmation, setDeleteConfirmation ] = useState( false );
28
+ const [ errorMessage, setErrorMessage ] = useState( '' );
29
+
31
30
  const variable = useVariable( editId );
32
31
  if ( ! variable ) {
33
32
  throw new Error( `Global color variable not found` );
34
33
  }
35
34
 
36
- const defaultRef = useRef< HTMLDivElement >( null );
37
- const anchorRef = usePopoverContentRef() ?? defaultRef;
38
-
39
35
  const [ color, setColor ] = useState( variable.value );
40
36
  const [ label, setLabel ] = useState( variable.label );
41
37
 
@@ -43,91 +39,119 @@ export const ColorVariableEdit = ( { onClose, onGoBack, onSubmit, editId }: Prop
43
39
  updateVariable( editId, {
44
40
  value: color,
45
41
  label,
46
- } ).then( () => {
47
- onSubmit?.();
48
- } );
42
+ } )
43
+ .then( () => {
44
+ maybeTriggerBoundPropChange();
45
+ onSubmit?.();
46
+ } )
47
+ .catch( ( error ) => {
48
+ setErrorMessage( error.message );
49
+ } );
49
50
  };
50
51
 
51
52
  const handleDelete = () => {
52
53
  deleteVariable( editId ).then( () => {
54
+ maybeTriggerBoundPropChange();
53
55
  onSubmit?.();
54
56
  } );
55
57
  };
56
58
 
57
- const noValueChanged = () => color === variable.value && label === variable.label;
58
- const hasEmptyValue = () => '' === color.trim() || '' === label.trim();
59
- const isSaveDisabled = () => noValueChanged() || hasEmptyValue();
59
+ const maybeTriggerBoundPropChange = () => {
60
+ if ( editId === assignedValue ) {
61
+ notifyBoundPropChange( editId );
62
+ }
63
+ };
64
+
65
+ const handleDeleteConfirmation = () => {
66
+ setDeleteConfirmation( true );
67
+ };
68
+
69
+ const closeDeleteDialog = () => () => {
70
+ setDeleteConfirmation( false );
71
+ };
60
72
 
61
73
  const actions = [];
62
74
 
63
75
  actions.push(
64
- <IconButton key="delete" size={ SIZE } aria-label={ __( 'Delete', 'elementor' ) } onClick={ handleDelete }>
76
+ <IconButton
77
+ key="delete"
78
+ size={ SIZE }
79
+ aria-label={ __( 'Delete', 'elementor' ) }
80
+ onClick={ handleDeleteConfirmation }
81
+ >
65
82
  <TrashIcon fontSize={ SIZE } />
66
83
  </IconButton>
67
84
  );
68
85
 
86
+ const hasEmptyValues = () => {
87
+ return ! color.trim() || ! label.trim();
88
+ };
89
+
90
+ const noValueChanged = () => {
91
+ return color === variable.value && label === variable.label;
92
+ };
93
+
94
+ const isSubmitDisabled = noValueChanged() || hasEmptyValues();
95
+
69
96
  return (
70
- <PopoverScrollableContent height="auto">
71
- <PopoverHeader
72
- title={ __( 'Edit variable', 'elementor' ) }
73
- onClose={ onClose }
74
- icon={
75
- <>
76
- { onGoBack && (
77
- <IconButton size={ SIZE } aria-label={ __( 'Go Back', 'elementor' ) } onClick={ onGoBack }>
78
- <ArrowLeftIcon fontSize={ SIZE } />
79
- </IconButton>
80
- ) }
81
- <BrushIcon fontSize={ SIZE } />
82
- </>
83
- }
84
- actions={ actions }
85
- />
86
-
87
- <Divider />
88
-
89
- <PopoverContent p={ 2 }>
90
- <Grid container gap={ 0.75 } alignItems="center">
91
- <Grid item xs={ 12 }>
92
- <FormLabel size="tiny">{ __( 'Name', 'elementor' ) }</FormLabel>
93
- </Grid>
94
- <Grid item xs={ 12 }>
95
- <TextField
96
- size="tiny"
97
- fullWidth
98
- value={ label }
99
- onChange={ ( e: React.ChangeEvent< HTMLInputElement > ) => setLabel( e.target.value ) }
100
- />
101
- </Grid>
102
- </Grid>
103
-
104
- <Grid container gap={ 0.75 } alignItems="center">
105
- <Grid item xs={ 12 }>
106
- <FormLabel size="tiny">{ __( 'Value', 'elementor' ) }</FormLabel>
107
- </Grid>
108
- <Grid item xs={ 12 }>
109
- <UnstableColorField
110
- size="tiny"
111
- fullWidth
112
- value={ color }
113
- onChange={ setColor }
114
- slotProps={ {
115
- colorPicker: {
116
- anchorEl: anchorRef.current,
117
- anchorOrigin: { vertical: 'top', horizontal: 'right' },
118
- transformOrigin: { vertical: 'top', horizontal: -10 },
119
- },
120
- } }
121
- />
122
- </Grid>
123
- </Grid>
124
- </PopoverContent>
125
-
126
- <CardActions sx={ { pt: 0.5, pb: 1 } }>
127
- <Button size="small" variant="contained" disabled={ isSaveDisabled() } onClick={ handleUpdate }>
128
- { __( 'Save', 'elementor' ) }
129
- </Button>
130
- </CardActions>
131
- </PopoverScrollableContent>
97
+ <>
98
+ <PopoverBody height="auto">
99
+ <PopoverHeader
100
+ title={ __( 'Edit variable', 'elementor' ) }
101
+ onClose={ onClose }
102
+ icon={
103
+ <>
104
+ { onGoBack && (
105
+ <IconButton
106
+ size={ SIZE }
107
+ aria-label={ __( 'Go Back', 'elementor' ) }
108
+ onClick={ onGoBack }
109
+ >
110
+ <ArrowLeftIcon fontSize={ SIZE } />
111
+ </IconButton>
112
+ ) }
113
+ <BrushIcon fontSize={ SIZE } />
114
+ </>
115
+ }
116
+ actions={ actions }
117
+ />
118
+
119
+ <Divider />
120
+
121
+ <PopoverContent p={ 2 }>
122
+ <LabelField
123
+ value={ label }
124
+ onChange={ ( value ) => {
125
+ setLabel( value );
126
+ setErrorMessage( '' );
127
+ } }
128
+ />
129
+ <ColorField
130
+ value={ color }
131
+ onChange={ ( value ) => {
132
+ setColor( value );
133
+ setErrorMessage( '' );
134
+ } }
135
+ />
136
+
137
+ { errorMessage && <FormHelperText error>{ errorMessage }</FormHelperText> }
138
+ </PopoverContent>
139
+
140
+ <CardActions sx={ { pt: 0.5, pb: 1 } }>
141
+ <Button size="small" variant="contained" disabled={ isSubmitDisabled } onClick={ handleUpdate }>
142
+ { __( 'Save', 'elementor' ) }
143
+ </Button>
144
+ </CardActions>
145
+ </PopoverBody>
146
+
147
+ { deleteConfirmation && (
148
+ <DeleteConfirmationDialog
149
+ open
150
+ label={ label }
151
+ onConfirm={ handleDelete }
152
+ closeDialog={ closeDeleteDialog() }
153
+ />
154
+ ) }
155
+ </>
132
156
  );
133
157
  };
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
- import { PopoverScrollableContent } from '@elementor/editor-editing-panel';
4
+ import { PopoverBody } from '@elementor/editor-editing-panel';
5
5
  import { PopoverHeader, PopoverMenuList, PopoverSearch, type VirtualizedItem } from '@elementor/editor-ui';
6
6
  import { BrushIcon, ColorFilterIcon, PlusIcon, SettingsIcon } from '@elementor/icons';
7
7
  import { Divider, IconButton } from '@elementor/ui';
@@ -76,7 +76,7 @@ export const ColorVariablesSelection = ( { closePopover, onAdd, onEdit, onSettin
76
76
  };
77
77
 
78
78
  return (
79
- <>
79
+ <PopoverBody>
80
80
  <PopoverHeader
81
81
  title={ __( 'Variables', 'elementor' ) }
82
82
  icon={ <ColorFilterIcon fontSize={ SIZE } /> }
@@ -94,37 +94,35 @@ export const ColorVariablesSelection = ( { closePopover, onAdd, onEdit, onSettin
94
94
 
95
95
  <Divider />
96
96
 
97
- <PopoverScrollableContent>
98
- { hasVariables && hasSearchResults && (
99
- <PopoverMenuList
100
- items={ items }
101
- onSelect={ handleSetColorVariable }
102
- onClose={ () => {} }
103
- selectedValue={ variable }
104
- data-testid="color-variables-list"
105
- menuListTemplate={ VariablesStyledMenuList }
106
- menuItemContentTemplate={ ( item: VirtualizedItem< 'item', string > ) => (
107
- <MenuItemContent item={ item } />
108
- ) }
109
- />
110
- ) }
111
-
112
- { ! hasSearchResults && hasVariables && (
113
- <NoSearchResults
114
- searchValue={ searchValue }
115
- onClear={ handleClearSearch }
116
- icon={ <BrushIcon fontSize="large" /> }
117
- />
118
- ) }
119
-
120
- { ! hasVariables && (
121
- <NoVariables
122
- title={ __( 'Create your first color variable', 'elementor' ) }
123
- icon={ <BrushIcon fontSize="large" /> }
124
- onAdd={ onAdd }
125
- />
126
- ) }
127
- </PopoverScrollableContent>
128
- </>
97
+ { hasVariables && hasSearchResults && (
98
+ <PopoverMenuList
99
+ items={ items }
100
+ onSelect={ handleSetColorVariable }
101
+ onClose={ () => {} }
102
+ selectedValue={ variable }
103
+ data-testid="color-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={ <BrushIcon fontSize="large" /> }
116
+ />
117
+ ) }
118
+
119
+ { ! hasVariables && (
120
+ <NoVariables
121
+ title={ __( 'Create your first color variable', 'elementor' ) }
122
+ icon={ <BrushIcon fontSize="large" /> }
123
+ onAdd={ onAdd }
124
+ />
125
+ ) }
126
+ </PopoverBody>
129
127
  );
130
128
  };
@@ -0,0 +1,54 @@
1
+ import * as React from 'react';
2
+ import { useRef, useState } from 'react';
3
+ import { FormHelperText, FormLabel, Grid, UnstableColorField } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { validateValue } from '../../utils/validations';
7
+ import { usePopoverContentRef } from '../variable-selection-popover.context';
8
+
9
+ type ColorFieldProps = {
10
+ value: string;
11
+ onChange: ( value: string ) => void;
12
+ };
13
+
14
+ export const ColorField = ( { value, onChange }: ColorFieldProps ) => {
15
+ const [ color, setColor ] = useState( value );
16
+ const [ errorMessage, setErrorMessage ] = useState( '' );
17
+
18
+ const defaultRef = useRef< HTMLDivElement >( null );
19
+ const anchorRef = usePopoverContentRef() ?? defaultRef;
20
+
21
+ const handleChange = ( newValue: string ) => {
22
+ setColor( newValue );
23
+
24
+ const errorMsg = validateValue( newValue );
25
+ setErrorMessage( errorMsg );
26
+
27
+ onChange( errorMsg ? '' : newValue );
28
+ };
29
+
30
+ return (
31
+ <Grid container gap={ 0.75 } alignItems="center">
32
+ <Grid item xs={ 12 }>
33
+ <FormLabel size="tiny">{ __( 'Value', 'elementor' ) }</FormLabel>
34
+ </Grid>
35
+ <Grid item xs={ 12 }>
36
+ <UnstableColorField
37
+ size="tiny"
38
+ fullWidth
39
+ value={ color }
40
+ onChange={ handleChange }
41
+ error={ errorMessage ?? undefined }
42
+ slotProps={ {
43
+ colorPicker: {
44
+ anchorEl: anchorRef.current,
45
+ anchorOrigin: { vertical: 'top', horizontal: 'right' },
46
+ transformOrigin: { vertical: 'top', horizontal: -10 },
47
+ },
48
+ } }
49
+ />
50
+ { errorMessage && <FormHelperText error>{ errorMessage }</FormHelperText> }
51
+ </Grid>
52
+ </Grid>
53
+ );
54
+ };
@@ -0,0 +1,85 @@
1
+ import * as React from 'react';
2
+ import { useRef, useState } from 'react';
3
+ import { FontFamilySelector } from '@elementor/editor-controls';
4
+ import { useFontFamilies, useSectionWidth } from '@elementor/editor-editing-panel';
5
+ import { ChevronDownIcon } from '@elementor/icons';
6
+ import {
7
+ bindPopover,
8
+ bindTrigger,
9
+ FormHelperText,
10
+ FormLabel,
11
+ Grid,
12
+ Popover,
13
+ UnstableTag,
14
+ usePopupState,
15
+ } from '@elementor/ui';
16
+ import { __ } from '@wordpress/i18n';
17
+
18
+ import { validateValue } from '../../utils/validations';
19
+ import { usePopoverContentRef } from '../variable-selection-popover.context';
20
+
21
+ type FontFieldProps = {
22
+ value: string;
23
+ onChange: ( value: string ) => void;
24
+ };
25
+
26
+ export const FontField = ( { value, onChange }: FontFieldProps ) => {
27
+ const [ fontFamily, setFontFamily ] = useState( value );
28
+ const [ errorMessage, setErrorMessage ] = useState( '' );
29
+
30
+ const defaultRef = useRef< HTMLDivElement >( null );
31
+ const anchorRef = usePopoverContentRef() ?? defaultRef;
32
+
33
+ const fontPopoverState = usePopupState( { variant: 'popover' } );
34
+
35
+ const fontFamilies = useFontFamilies();
36
+ const sectionWidth = useSectionWidth();
37
+
38
+ const handleChange = ( newValue: string ) => {
39
+ setFontFamily( newValue );
40
+
41
+ const errorMsg = validateValue( newValue );
42
+ setErrorMessage( errorMsg );
43
+
44
+ onChange( errorMsg ? '' : newValue );
45
+ };
46
+
47
+ const handleFontFamilyChange = ( newFontFamily: string ) => {
48
+ handleChange( newFontFamily );
49
+ fontPopoverState.close();
50
+ };
51
+
52
+ return (
53
+ <Grid container gap={ 0.75 } alignItems="center">
54
+ <Grid item xs={ 12 }>
55
+ <FormLabel size="tiny">{ __( 'Value', 'elementor' ) }</FormLabel>
56
+ </Grid>
57
+ <Grid item xs={ 12 }>
58
+ <UnstableTag
59
+ variant="outlined"
60
+ label={ fontFamily }
61
+ endIcon={ <ChevronDownIcon fontSize="tiny" /> }
62
+ { ...bindTrigger( fontPopoverState ) }
63
+ fullWidth
64
+ />
65
+ <Popover
66
+ disablePortal
67
+ disableScrollLock
68
+ anchorEl={ anchorRef.current }
69
+ anchorOrigin={ { vertical: 'top', horizontal: 'right' } }
70
+ transformOrigin={ { vertical: 'top', horizontal: -20 } }
71
+ { ...bindPopover( fontPopoverState ) }
72
+ >
73
+ <FontFamilySelector
74
+ fontFamilies={ fontFamilies }
75
+ fontFamily={ fontFamily }
76
+ onFontFamilyChange={ handleFontFamilyChange }
77
+ onClose={ fontPopoverState.close }
78
+ sectionWidth={ sectionWidth }
79
+ />
80
+ </Popover>
81
+ { errorMessage && <FormHelperText error>{ errorMessage }</FormHelperText> }
82
+ </Grid>
83
+ </Grid>
84
+ );
85
+ };