@elementor/editor-global-classes 0.21.0 → 0.22.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.
- package/CHANGELOG.md +56 -0
- package/dist/index.js +320 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +301 -147
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -14
- package/src/components/class-manager/class-item.tsx +238 -0
- package/src/components/class-manager/class-manager-class-not-found.tsx +56 -0
- package/src/components/class-manager/class-manager-panel.tsx +45 -4
- package/src/components/class-manager/class-manager-search.tsx +33 -0
- package/src/components/class-manager/global-classes-list.tsx +70 -239
|
@@ -1,42 +1,48 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useMemo } from 'react';
|
|
3
3
|
import { type StyleDefinitionID } from '@elementor/editor-styles';
|
|
4
|
-
import { validateStyleLabel } from '@elementor/editor-styles-repository';
|
|
5
|
-
import { EditableField, EllipsisWithTooltip, MenuListItem, useEditable, WarningInfotip } from '@elementor/editor-ui';
|
|
6
|
-
import { DotsVerticalIcon } from '@elementor/icons';
|
|
7
4
|
import { __useDispatch as useDispatch } from '@elementor/store';
|
|
8
|
-
import {
|
|
9
|
-
bindMenu,
|
|
10
|
-
bindTrigger,
|
|
11
|
-
Box,
|
|
12
|
-
IconButton,
|
|
13
|
-
List,
|
|
14
|
-
ListItemButton,
|
|
15
|
-
type ListItemButtonProps,
|
|
16
|
-
Menu,
|
|
17
|
-
Stack,
|
|
18
|
-
styled,
|
|
19
|
-
type Theme,
|
|
20
|
-
Tooltip,
|
|
21
|
-
Typography,
|
|
22
|
-
type TypographyProps,
|
|
23
|
-
usePopupState,
|
|
24
|
-
} from '@elementor/ui';
|
|
5
|
+
import { List, Stack, styled, Typography, type TypographyProps } from '@elementor/ui';
|
|
25
6
|
import { __ } from '@wordpress/i18n';
|
|
26
7
|
|
|
27
8
|
import { useClassesOrder } from '../../hooks/use-classes-order';
|
|
28
9
|
import { useOrderedClasses } from '../../hooks/use-ordered-classes';
|
|
29
10
|
import { slice } from '../../store';
|
|
30
|
-
import {
|
|
11
|
+
import { ClassItem } from './class-item';
|
|
12
|
+
import { CssClassNotFound } from './class-manager-class-not-found';
|
|
13
|
+
import { DeleteConfirmationProvider } from './delete-confirmation-dialog';
|
|
31
14
|
import { FlippedColorSwatchIcon } from './flipped-color-swatch-icon';
|
|
32
|
-
import { SortableItem, SortableProvider
|
|
15
|
+
import { SortableItem, SortableProvider } from './sortable';
|
|
33
16
|
|
|
34
|
-
|
|
17
|
+
type GlobalClassesListProps = {
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
searchValue: string;
|
|
20
|
+
onSearch: ( searchValue: string ) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const GlobalClassesList = ( { disabled, searchValue, onSearch }: GlobalClassesListProps ) => {
|
|
35
24
|
const cssClasses = useOrderedClasses();
|
|
36
25
|
const dispatch = useDispatch();
|
|
37
26
|
|
|
38
27
|
const [ classesOrder, reorderClasses ] = useReorder();
|
|
39
28
|
|
|
29
|
+
const lowercaseLabels = useMemo(
|
|
30
|
+
() =>
|
|
31
|
+
cssClasses.map( ( cssClass ) => ( {
|
|
32
|
+
...cssClass,
|
|
33
|
+
lowerLabel: cssClass.label.toLowerCase(),
|
|
34
|
+
} ) ),
|
|
35
|
+
[ cssClasses ]
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const filteredClasses = useMemo( () => {
|
|
39
|
+
return searchValue.length > 1
|
|
40
|
+
? lowercaseLabels.filter( ( cssClass ) =>
|
|
41
|
+
cssClass.lowerLabel.toLowerCase().includes( searchValue.toLowerCase() )
|
|
42
|
+
)
|
|
43
|
+
: cssClasses;
|
|
44
|
+
}, [ searchValue, cssClasses, lowercaseLabels ] );
|
|
45
|
+
|
|
40
46
|
useEffect( () => {
|
|
41
47
|
const handler = ( event: KeyboardEvent ) => {
|
|
42
48
|
if ( event.key === 'z' && ( event.ctrlKey || event.metaKey ) ) {
|
|
@@ -61,195 +67,44 @@ export const GlobalClassesList = ( { disabled }: { disabled?: boolean } ) => {
|
|
|
61
67
|
|
|
62
68
|
return (
|
|
63
69
|
<DeleteConfirmationProvider>
|
|
64
|
-
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
{ filteredClasses.length <= 0 && searchValue.length > 1 ? (
|
|
71
|
+
<CssClassNotFound onClear={ () => onSearch( '' ) } searchValue={ searchValue } />
|
|
72
|
+
) : (
|
|
73
|
+
<List sx={ { display: 'flex', flexDirection: 'column', gap: 0.5 } }>
|
|
74
|
+
<SortableProvider value={ classesOrder } onChange={ reorderClasses }>
|
|
75
|
+
{ filteredClasses?.map( ( { id, label } ) => {
|
|
76
|
+
return (
|
|
77
|
+
<SortableItem key={ id } id={ id }>
|
|
78
|
+
{ ( { isDragged, isDragPlaceholder, triggerProps, triggerStyle } ) => (
|
|
79
|
+
<ClassItem
|
|
80
|
+
isSearchActive={ searchValue.length < 2 }
|
|
81
|
+
id={ id }
|
|
82
|
+
label={ label }
|
|
83
|
+
renameClass={ ( newLabel: string ) => {
|
|
84
|
+
dispatch(
|
|
85
|
+
slice.actions.update( {
|
|
86
|
+
style: {
|
|
87
|
+
id,
|
|
88
|
+
label: newLabel,
|
|
89
|
+
},
|
|
90
|
+
} )
|
|
91
|
+
);
|
|
92
|
+
} }
|
|
93
|
+
selected={ isDragged }
|
|
94
|
+
disabled={ disabled || isDragPlaceholder }
|
|
95
|
+
sortableTriggerProps={ { ...triggerProps, style: triggerStyle } }
|
|
96
|
+
/>
|
|
97
|
+
) }
|
|
98
|
+
</SortableItem>
|
|
75
99
|
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{ ( { isDragged, isDragPlaceholder, triggerProps, triggerStyle } ) => (
|
|
81
|
-
<ClassItem
|
|
82
|
-
id={ id }
|
|
83
|
-
label={ label }
|
|
84
|
-
renameClass={ renameClass }
|
|
85
|
-
selected={ isDragged }
|
|
86
|
-
disabled={ disabled || isDragPlaceholder }
|
|
87
|
-
sortableTriggerProps={ { ...triggerProps, style: triggerStyle } }
|
|
88
|
-
/>
|
|
89
|
-
) }
|
|
90
|
-
</SortableItem>
|
|
91
|
-
);
|
|
92
|
-
} ) }
|
|
93
|
-
</SortableProvider>
|
|
94
|
-
</List>
|
|
100
|
+
} ) }
|
|
101
|
+
</SortableProvider>
|
|
102
|
+
</List>
|
|
103
|
+
) }
|
|
95
104
|
</DeleteConfirmationProvider>
|
|
96
105
|
);
|
|
97
106
|
};
|
|
98
107
|
|
|
99
|
-
const useReorder = () => {
|
|
100
|
-
const dispatch = useDispatch();
|
|
101
|
-
const order = useClassesOrder();
|
|
102
|
-
|
|
103
|
-
const reorder = ( newIds: StyleDefinitionID[] ) => {
|
|
104
|
-
dispatch( slice.actions.setOrder( newIds ) );
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
return [ order, reorder ] as const;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
type ClassItemProps = React.PropsWithChildren< {
|
|
111
|
-
id: string;
|
|
112
|
-
label: string;
|
|
113
|
-
renameClass: ( newLabel: string ) => void;
|
|
114
|
-
selected?: boolean;
|
|
115
|
-
disabled?: boolean;
|
|
116
|
-
sortableTriggerProps: SortableTriggerProps;
|
|
117
|
-
} >;
|
|
118
|
-
|
|
119
|
-
const ClassItem = ( { id, label, renameClass, selected, disabled, sortableTriggerProps }: ClassItemProps ) => {
|
|
120
|
-
const itemRef = useRef< HTMLElement >( null );
|
|
121
|
-
|
|
122
|
-
const {
|
|
123
|
-
ref: editableRef,
|
|
124
|
-
openEditMode,
|
|
125
|
-
isEditing,
|
|
126
|
-
error,
|
|
127
|
-
getProps: getEditableProps,
|
|
128
|
-
} = useEditable( {
|
|
129
|
-
value: label,
|
|
130
|
-
onSubmit: renameClass,
|
|
131
|
-
validation: validateLabel,
|
|
132
|
-
} );
|
|
133
|
-
|
|
134
|
-
const { openDialog } = useDeleteConfirmation();
|
|
135
|
-
|
|
136
|
-
const popupState = usePopupState( {
|
|
137
|
-
variant: 'popover',
|
|
138
|
-
disableAutoFocus: true,
|
|
139
|
-
} );
|
|
140
|
-
|
|
141
|
-
const isSelected = ( selected || popupState.isOpen ) && ! disabled;
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<>
|
|
145
|
-
<Stack p={ 0 }>
|
|
146
|
-
<WarningInfotip
|
|
147
|
-
open={ Boolean( error ) }
|
|
148
|
-
text={ error ?? '' }
|
|
149
|
-
placement="bottom"
|
|
150
|
-
width={ itemRef.current?.getBoundingClientRect().width }
|
|
151
|
-
offset={ [ 0, -15 ] }
|
|
152
|
-
>
|
|
153
|
-
<StyledListItemButton
|
|
154
|
-
ref={ itemRef }
|
|
155
|
-
dense
|
|
156
|
-
disableGutters
|
|
157
|
-
showActions={ isSelected || isEditing }
|
|
158
|
-
shape="rounded"
|
|
159
|
-
onDoubleClick={ openEditMode }
|
|
160
|
-
selected={ isSelected }
|
|
161
|
-
disabled={ disabled }
|
|
162
|
-
focusVisibleClassName="visible-class-item"
|
|
163
|
-
>
|
|
164
|
-
<SortableTrigger { ...sortableTriggerProps } />
|
|
165
|
-
<Indicator isActive={ isEditing } isError={ !! error }>
|
|
166
|
-
{ isEditing ? (
|
|
167
|
-
<EditableField
|
|
168
|
-
ref={ editableRef }
|
|
169
|
-
as={ Typography }
|
|
170
|
-
variant="caption"
|
|
171
|
-
{ ...getEditableProps() }
|
|
172
|
-
/>
|
|
173
|
-
) : (
|
|
174
|
-
<EllipsisWithTooltip title={ label } as={ Typography } variant="caption" />
|
|
175
|
-
) }
|
|
176
|
-
</Indicator>
|
|
177
|
-
<Tooltip
|
|
178
|
-
placement="top"
|
|
179
|
-
className={ 'class-item-more-actions' }
|
|
180
|
-
title={ __( 'More actions', 'elementor' ) }
|
|
181
|
-
>
|
|
182
|
-
<IconButton size="tiny" { ...bindTrigger( popupState ) } aria-label="More actions">
|
|
183
|
-
<DotsVerticalIcon fontSize="tiny" />
|
|
184
|
-
</IconButton>
|
|
185
|
-
</Tooltip>
|
|
186
|
-
</StyledListItemButton>
|
|
187
|
-
</WarningInfotip>
|
|
188
|
-
</Stack>
|
|
189
|
-
<Menu
|
|
190
|
-
{ ...bindMenu( popupState ) }
|
|
191
|
-
anchorOrigin={ {
|
|
192
|
-
vertical: 'bottom',
|
|
193
|
-
horizontal: 'right',
|
|
194
|
-
} }
|
|
195
|
-
transformOrigin={ {
|
|
196
|
-
vertical: 'top',
|
|
197
|
-
horizontal: 'right',
|
|
198
|
-
} }
|
|
199
|
-
>
|
|
200
|
-
<MenuListItem
|
|
201
|
-
sx={ { minWidth: '160px' } }
|
|
202
|
-
onClick={ () => {
|
|
203
|
-
popupState.close();
|
|
204
|
-
openEditMode();
|
|
205
|
-
} }
|
|
206
|
-
>
|
|
207
|
-
<Typography variant="caption" sx={ { color: 'text.primary' } }>
|
|
208
|
-
{ __( 'Rename', 'elementor' ) }
|
|
209
|
-
</Typography>
|
|
210
|
-
</MenuListItem>
|
|
211
|
-
<MenuListItem
|
|
212
|
-
onClick={ () => {
|
|
213
|
-
popupState.close();
|
|
214
|
-
openDialog( { id, label } );
|
|
215
|
-
} }
|
|
216
|
-
>
|
|
217
|
-
<Typography variant="caption" sx={ { color: 'error.light' } }>
|
|
218
|
-
{ __( 'Delete', 'elementor' ) }
|
|
219
|
-
</Typography>
|
|
220
|
-
</MenuListItem>
|
|
221
|
-
</Menu>
|
|
222
|
-
</>
|
|
223
|
-
);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// Custom styles for sortable list item, until the component is available in the UI package.
|
|
227
|
-
const StyledListItemButton = styled( ListItemButton, {
|
|
228
|
-
shouldForwardProp: ( prop: string ) => ! [ 'showActions' ].includes( prop ),
|
|
229
|
-
} )< ListItemButtonProps & { showActions: boolean } >(
|
|
230
|
-
( { showActions } ) => `
|
|
231
|
-
min-height: 36px;
|
|
232
|
-
|
|
233
|
-
&.visible-class-item {
|
|
234
|
-
box-shadow: none !important;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.class-item-more-actions, .class-item-sortable-trigger {
|
|
238
|
-
visibility: ${ showActions ? 'visible' : 'hidden' };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
.class-item-sortable-trigger {
|
|
242
|
-
visibility: ${ showActions ? 'visible' : 'hidden' };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
&:hover&:not(:disabled) {
|
|
246
|
-
.class-item-more-actions, .class-item-sortable-trigger {
|
|
247
|
-
visibility: visible;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
`
|
|
251
|
-
);
|
|
252
|
-
|
|
253
108
|
const EmptyState = () => (
|
|
254
109
|
<Stack alignItems="center" gap={ 1.5 } pt={ 10 } px={ 0.5 } maxWidth="260px" margin="auto">
|
|
255
110
|
<FlippedColorSwatchIcon fontSize="large" />
|
|
@@ -272,37 +127,13 @@ const StyledHeader = styled( Typography )< TypographyProps >( ( { theme, variant
|
|
|
272
127
|
},
|
|
273
128
|
} ) );
|
|
274
129
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
display: 'flex',
|
|
279
|
-
width: '100%',
|
|
280
|
-
flexGrow: 1,
|
|
281
|
-
borderRadius: theme.spacing( 0.5 ),
|
|
282
|
-
border: getIndicatorBorder( { isActive, isError, theme } ),
|
|
283
|
-
padding: `0 ${ theme.spacing( 1 ) }`,
|
|
284
|
-
marginLeft: isActive ? theme.spacing( 1 ) : 0,
|
|
285
|
-
minWidth: 0,
|
|
286
|
-
} ) );
|
|
287
|
-
|
|
288
|
-
const getIndicatorBorder = ( { isActive, isError, theme }: { isActive: boolean; isError: boolean; theme: Theme } ) => {
|
|
289
|
-
if ( isError ) {
|
|
290
|
-
return `2px solid ${ theme.palette.error.main }`;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if ( isActive ) {
|
|
294
|
-
return `2px solid ${ theme.palette.secondary.main }`;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return 'none';
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
const validateLabel = ( newLabel: string ) => {
|
|
301
|
-
const result = validateStyleLabel( newLabel, 'rename' );
|
|
130
|
+
const useReorder = () => {
|
|
131
|
+
const dispatch = useDispatch();
|
|
132
|
+
const order = useClassesOrder();
|
|
302
133
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
134
|
+
const reorder = ( newIds: StyleDefinitionID[] ) => {
|
|
135
|
+
dispatch( slice.actions.setOrder( newIds ) );
|
|
136
|
+
};
|
|
306
137
|
|
|
307
|
-
return
|
|
138
|
+
return [ order, reorder ] as const;
|
|
308
139
|
};
|