Skip to content

Commit 9fadba6

Browse files
Merge branch 'development' into feat/issue-3063
2 parents 36e0e2b + 8cd27f3 commit 9fadba6

File tree

50 files changed

+2728
-485
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2728
-485
lines changed

assets/apps/customizer-controls/src/builder/components/Builder.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ const Builder: React.FC<Props> = ({ value, hidden, portalMount }) => {
138138
setMobileOverlayDismissed(true);
139139
}}
140140
>
141-
{__('Enable', 'neve-pro-addon')}
141+
{__('Enable', 'neve')}
142142
</Button>
143143
</div>
144144
)}

assets/apps/customizer-controls/src/builder/components/ComponentsPopover.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ const ComponentsPopover: React.FC<Props> = ({
8080
});
8181
};
8282

83+
const getFilteredUpsells = () => {
84+
if (!searchQuery) {
85+
return upsells;
86+
}
87+
88+
return upsells.filter(({ name }) => {
89+
return name.toLowerCase().includes(searchQuery);
90+
});
91+
};
92+
8393
const renderItem = (item: ItemInterface, idx: number) => {
8494
if (!item.id) {
8595
return null;
@@ -113,10 +123,15 @@ const ComponentsPopover: React.FC<Props> = ({
113123
const renderItems = () => {
114124
const themeItems = getSidebarItems();
115125
const boosterItems = getSidebarItems(true);
126+
const filteredUpsells = getFilteredUpsells();
116127

117128
let noComponents = null;
118129

119-
if (themeItems.length === 0 && boosterItems.length === 0) {
130+
if (
131+
themeItems.length === 0 &&
132+
boosterItems.length === 0 &&
133+
filteredUpsells.length === 0
134+
) {
120135
noComponents = (
121136
<div className="no-components">
122137
<span>
@@ -156,13 +171,13 @@ const ComponentsPopover: React.FC<Props> = ({
156171
)}
157172
</>
158173
)}
159-
{boosterItems.length < 1 && upsells.length > 0 && (
174+
{boosterItems.length < 1 && filteredUpsells.length > 0 && (
160175
<>
161176
<h4>
162177
{__('PRO', 'neve')} {__('Components', 'neve')}
163178
</h4>
164179
<div className="items-popover-list upsell-list">
165-
{upsells.map(({ name, icon }, idx) => {
180+
{filteredUpsells.map(({ name, icon }, idx) => {
166181
return renderUpsell(idx, icon, name);
167182
})}
168183
</div>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useCallback, useRef, useEffect } from '@wordpress/element';
2+
3+
/**
4+
* Custom hook for keyboard-based sorting of list items.
5+
*
6+
* @param {number} index - Current item index in the list
7+
* @param {number} totalItems - Total number of items in the list
8+
* @param {Function} onMove - Callback function to move item (receives fromIndex, toIndex)
9+
* @param {boolean} isActive - External state for whether keyboard mode is active
10+
* @param {Function} setIsActive - Function to set the active state
11+
* @return {Object} Hook state and handlers
12+
*/
13+
const useKeyboardSorting = (
14+
index,
15+
totalItems,
16+
onMove,
17+
isActive,
18+
setIsActive
19+
) => {
20+
const handleRef = useRef(null);
21+
const previousIndexRef = useRef(index);
22+
23+
// Handle keyboard events
24+
const handleKeyDown = useCallback(
25+
(e) => {
26+
if (e.key === ' ' || e.key === 'Spacebar') {
27+
e.preventDefault();
28+
setIsActive(!isActive);
29+
return;
30+
}
31+
32+
if (!isActive) {
33+
return;
34+
}
35+
36+
if (e.key === 'ArrowUp' && index > 0) {
37+
e.preventDefault();
38+
const newIndex = index - 1;
39+
previousIndexRef.current = index;
40+
onMove(index, newIndex);
41+
}
42+
43+
if (e.key === 'ArrowDown' && index < totalItems - 1) {
44+
e.preventDefault();
45+
const newIndex = index + 1;
46+
previousIndexRef.current = index;
47+
onMove(index, newIndex);
48+
}
49+
if (e.key === 'Escape') {
50+
e.preventDefault();
51+
setIsActive(false);
52+
}
53+
},
54+
[isActive, index, totalItems, onMove, setIsActive]
55+
);
56+
57+
const handleBlur = useCallback(() => {
58+
setIsActive(false);
59+
}, [setIsActive]);
60+
61+
useEffect(() => {
62+
if (
63+
isActive &&
64+
handleRef.current &&
65+
previousIndexRef.current !== index
66+
) {
67+
// Small delay to ensure DOM has updated
68+
window.requestAnimationFrame(() => {
69+
if (handleRef.current) {
70+
handleRef.current.focus();
71+
}
72+
});
73+
}
74+
previousIndexRef.current = index;
75+
}, [index, isActive]);
76+
77+
return {
78+
handleRef,
79+
handleKeyDown,
80+
handleBlur,
81+
};
82+
};
83+
84+
export default useKeyboardSorting;

assets/apps/customizer-controls/src/controls.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,50 @@ const checkHasElementorTemplates = () => {
350350
}
351351
};
352352

353+
/**
354+
* Find the Scroll to top button within the customizer preview.
355+
*/
356+
function findScrollToTopBtn() {
357+
const iframeElement = document.querySelector('#customize-preview iframe');
358+
359+
if (!iframeElement) {
360+
return;
361+
}
362+
363+
const scrollToTopBtn =
364+
iframeElement.contentWindow.document.querySelector('#scroll-to-top');
365+
366+
return scrollToTopBtn;
367+
}
368+
369+
/**
370+
* Show the Scroll to Top button as soon as the user enters the section in Customizer.
371+
*/
372+
function previewScrollToTopChanges() {
373+
wp.customize.section('neve_scroll_to_top', (section) => {
374+
section.expanded.bind((isExpanded) => {
375+
const scrollToTopBtn = findScrollToTopBtn();
376+
377+
if (!scrollToTopBtn) {
378+
return;
379+
}
380+
381+
// If Scroll to top customizer section is expanded
382+
if (isExpanded) {
383+
wp.customize.previewer.bind('ready', () => {
384+
wp.customize.previewer.send('nv-opened-stt', true);
385+
});
386+
scrollToTopBtn.style.visibility = 'visible';
387+
scrollToTopBtn.style.opacity = '1';
388+
} else {
389+
// Hide the button when we leave the section
390+
scrollToTopBtn.style.visibility = 'hidden';
391+
scrollToTopBtn.style.opacity = '0';
392+
}
393+
});
394+
});
395+
}
396+
353397
window.wp.customize.bind('ready', () => {
354398
initStarterContentNotice();
355399
initDocSection();
@@ -365,6 +409,7 @@ window.wp.customize.bind('ready', () => {
365409
initSearchCustomizer();
366410
initLocalGoogleFonts();
367411
initStyleBookButton();
412+
previewScrollToTopChanges();
368413
});
369414

370415
window.HFG = {

assets/apps/customizer-controls/src/customizer-search/MainSearch.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { createPortal } from '@wordpress/element';
22
import React, { useState } from 'react';
3-
import SearchToggle from './SearchToggle';
43
import SearchComponent, { Control } from './SearchComponent';
54
import SearchResults from './SearchResults';
65

@@ -20,23 +19,13 @@ type MainSearchProps = {
2019
* @class
2120
*/
2221
const MainSearch: React.FC<MainSearchProps> = ({ search, button, results }) => {
23-
const [isOpened, setIsOpened] = useState(false);
2422
const [query, setQuery] = useState('');
2523
const [matchResults, setMatchResults] = useState([] as Control[]);
2624

2725
return (
2826
<>
29-
{createPortal(
30-
<SearchToggle
31-
onToggle={() => {
32-
setIsOpened(!isOpened);
33-
}}
34-
/>,
35-
button
36-
)}
3727
{createPortal(
3828
<SearchComponent
39-
isOpened={isOpened}
4029
search={query}
4130
setSearch={setQuery}
4231
matchResults={matchResults}

assets/apps/customizer-controls/src/customizer-search/SearchComponent.tsx

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ declare global {
6060
* Type SearchComponentProps
6161
*/
6262
type SearchComponentProps = {
63-
isOpened: boolean;
6463
search: string;
6564
setSearch: (value: string) => void;
6665
matchResults: Control[];
@@ -75,7 +74,6 @@ type SearchComponentProps = {
7574
* @class
7675
*/
7776
const SearchComponent: React.FC<SearchComponentProps> = ({
78-
isOpened,
7977
search,
8078
setSearch,
8179
matchResults,
@@ -128,24 +126,6 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
128126
});
129127
}, []);
130128

131-
/**
132-
* This `useEffect()` is being used to listen for the toggleEvent
133-
* from `SearchToggle` component.
134-
*/
135-
useEffect(() => {
136-
if (isOpened) {
137-
document
138-
.getElementById('neve-customize-search-field')
139-
?.classList.add('visible');
140-
document.getElementById('nv-customizer-search-input')?.focus();
141-
} else {
142-
document
143-
.getElementById('neve-customize-search-field')
144-
?.classList.remove('visible');
145-
clearField();
146-
}
147-
}, [isOpened]);
148-
149129
useEffect(() => {
150130
if (search === '') {
151131
if (customizerPanels?.classList.contains('search-not-found')) {
@@ -217,15 +197,12 @@ const SearchComponent: React.FC<SearchComponentProps> = ({
217197
return (
218198
<>
219199
<span className="accordion-section">
220-
<span className="search-input">
221-
{__('Search', 'neve') +
222-
' ' +
223-
__('Settings', 'neve').toLowerCase()}
224-
</span>
225200
<span className="nv-search-wrap">
226201
<input
227202
type="text"
228-
placeholder={__('Search', 'neve')}
203+
placeholder={
204+
__('Search', 'neve') + ' ' + __('Settings', 'neve')
205+
}
229206
id="nv-customizer-search-input"
230207
className="nv-customizer-search-input"
231208
value={search}

assets/apps/customizer-controls/src/customizer-search/SearchToggle.tsx

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)