PluginProbe ʕ •ᴥ•ʔ
Starter Sites & Templates by Neve / trunk
Starter Sites & Templates by Neve vtrunk
1.4.0 1.3.0 1.2.29 1.2.28 1.2.6 1.2.7 1.2.8 1.2.9 trunk 1.0.10 1.0.11 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.20 1.1.21 1.1.22 1.1.23 1.1.24 1.1.25 1.1.26 1.1.27 1.1.28 1.1.29 1.1.3 1.1.30 1.1.31 1.1.32 1.1.33 1.1.34 1.1.35 1.1.36 1.1.37 1.1.38 1.1.39 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.3 1.2.4 1.2.5
templates-patterns-collection / onboarding / src / Components / Filters.js
templates-patterns-collection / onboarding / src / Components Last commit date
CustomizeControls 2 months ago Steps 2 weeks ago App.js 2 years ago CategoryButtons.js 2 weeks ago CustomTooltip.js 2 years ago EditorSelector.js 2 years ago FeaturesList.js 1 year ago Filters.js 2 weeks ago Header.js 2 weeks ago ImportError.js 2 years ago ImportForm.js 1 year ago ImportMock.js 2 years ago ImportProgress.js 1 year ago Onboarding.js 2 weeks ago ProgressBar.js 2 years ago Search.js 2 weeks ago SitePreview.js 2 years ago SiteSettings.js 2 months ago Sites.js 2 weeks ago StarterSiteCard.js 2 weeks ago Toast.js 2 weeks ago WelcomeMock.js 2 years ago
Filters.js
238 lines
1 import { __ } from '@wordpress/i18n';
2 import { SelectControl } from '@wordpress/components';
3 import { useEffect, useMemo, useRef, useState } from '@wordpress/element';
4 import { useDispatch, useSelect } from '@wordpress/data';
5 import Search from './Search';
6 import CategoryButtons from './CategoryButtons';
7 import { ONBOARDING_CAT } from '../utils/common';
8
9 const hashColor = ( value = '' ) => {
10 let hash = 0;
11 for ( let index = 0; index < value.length; index++ ) {
12 hash = value.charCodeAt( index ) + ( ( hash << 5 ) - hash );
13 }
14
15 return Math.abs( hash );
16 };
17
18 const swatchBackground = ( colorSlug = '' ) => {
19 const normalized = String( colorSlug ).trim().toLowerCase();
20 if ( ! normalized ) {
21 return 'linear-gradient(135deg, #cbd5e1 0%, #64748b 100%)';
22 }
23
24 if (
25 /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test( normalized ) ||
26 /^(rgb|rgba|hsl|hsla)\(/i.test( normalized )
27 ) {
28 return normalized;
29 }
30
31 if (
32 typeof window !== 'undefined' &&
33 window.CSS &&
34 typeof window.CSS.supports === 'function' &&
35 window.CSS.supports( 'color', normalized )
36 ) {
37 return normalized;
38 }
39
40 const hue = hashColor( normalized ) % 360;
41 const hueShift = ( hue + 24 ) % 360;
42
43 return `linear-gradient(135deg, hsl(${ hue } 72% 72%) 0%, hsl(${ hueShift } 72% 44%) 100%)`;
44 };
45
46 const Filters = () => {
47 const [ isColorOpen, setIsColorOpen ] = useState( false );
48 const colorFilterRef = useRef( null );
49 const { sortBy, searchQuery, editor, sites, selectedColors } = useSelect( ( select ) => ( {
50 sortBy: select( 'ti-onboarding' ).getSortBy(),
51 searchQuery: select( 'ti-onboarding' ).getSearchQuery(),
52 editor: select( 'ti-onboarding' ).getCurrentEditor(),
53 sites: select( 'ti-onboarding' ).getSites(),
54 selectedColors: select( 'ti-onboarding' ).getSelectedColors(),
55 } ) );
56 const { setSortBy, setSelectedColors } = useDispatch( 'ti-onboarding' );
57
58 const categories = {
59 all: __( 'All', 'templates-patterns-collection' ),
60 free: __( 'Free', 'templates-patterns-collection' ),
61 ...ONBOARDING_CAT,
62 };
63
64 const trimmed = ( searchQuery || '' ).trim();
65 const showHint =
66 trimmed.length > 0 && trimmed.length <= 12 && ! trimmed.includes( ' ' );
67 const availableColors = useMemo( () => {
68 const builderSites = Object.values( sites?.sites?.[ editor ] || {} );
69 const seen = new Set();
70 const colors = [];
71
72 builderSites.forEach( ( site ) => {
73 if ( ! Array.isArray( site?.colors ) ) {
74 return;
75 }
76
77 site.colors.forEach( ( color ) => {
78 const normalized = String( color || '' ).trim().toLowerCase();
79 if ( ! normalized || seen.has( normalized ) ) {
80 return;
81 }
82
83 seen.add( normalized );
84 colors.push( normalized );
85 } );
86 } );
87
88 return colors;
89 }, [ sites, editor ] );
90 const sortOptions = [
91 {
92 label: __( 'Recommended', 'templates-patterns-collection' ),
93 value: 'recommended',
94 },
95 {
96 label: __( 'Popular', 'templates-patterns-collection' ),
97 value: 'popular',
98 },
99 ...( editor === 'gutenberg'
100 ? [
101 {
102 label: __( 'New first', 'templates-patterns-collection' ),
103 value: 'new',
104 },
105 ]
106 : [] ),
107 ];
108
109 useEffect( () => {
110 if ( ! isColorOpen ) {
111 return undefined;
112 }
113
114 const handleOutsideClick = ( event ) => {
115 if ( colorFilterRef.current?.contains( event.target ) ) {
116 return;
117 }
118
119 setIsColorOpen( false );
120 };
121
122 document.addEventListener( 'mousedown', handleOutsideClick );
123
124 return () => {
125 document.removeEventListener( 'mousedown', handleOutsideClick );
126 };
127 }, [ isColorOpen ] );
128
129 return (
130 <div className="ob-filters">
131 <div className="ob-search-row">
132 <Search
133 placeholder={ __(
134 'Describe your site — e.g. "law firm for personal injury cases"',
135 'templates-patterns-collection'
136 ) }
137 />
138 { showHint && (
139 <p className="ob-search-hint">
140 { __(
141 'Add a detail or two for sharper matches — try what you do, who it\'s for, or where.',
142 'templates-patterns-collection'
143 ) }
144 </p>
145 ) }
146 </div>
147 <div className="ob-filters-bar">
148 <CategoryButtons categories={ categories } />
149 { availableColors.length > 0 && (
150 <div
151 ref={ colorFilterRef }
152 className={
153 isColorOpen ? 'ob-color-filter is-open' : 'ob-color-filter'
154 }
155 >
156 <button
157 type="button"
158 className={
159 selectedColors.length
160 ? 'ob-color-filter__trigger has-selection'
161 : 'ob-color-filter__trigger'
162 }
163 aria-expanded={ isColorOpen }
164 onClick={ () => setIsColorOpen( ( open ) => ! open ) }
165 >
166 <span className="ob-color-filter__wheel" aria-hidden="true" />
167 <span className="ob-color-filter__label">
168 { selectedColors.length
169 ? `${ selectedColors.length } ${ __(
170 'selected',
171 'templates-patterns-collection'
172 ) }`
173 : __( 'Color', 'templates-patterns-collection' ) }
174 </span>
175 <span className="ob-color-filter__chevron" aria-hidden="true">
176
177 </span>
178 </button>
179 <div className="ob-color-filter__panel">
180 { availableColors.map( ( color ) => (
181 <button
182 type="button"
183 key={ color }
184 className={
185 selectedColors.includes( color )
186 ? 'ob-color-filter__option is-active'
187 : 'ob-color-filter__option'
188 }
189 onClick={ () => {
190 setSelectedColors(
191 selectedColors.includes( color )
192 ? selectedColors.filter( ( item ) => item !== color )
193 : [ ...selectedColors, color ]
194 );
195 } }
196 >
197 <span
198 className="ob-color-filter__swatch"
199 style={ {
200 background: swatchBackground( color ),
201 } }
202 />
203 <span className="ob-color-filter__name">{ color }</span>
204 </button>
205 ) ) }
206 { selectedColors.length > 0 && (
207 <button
208 type="button"
209 className="ob-color-filter__clear"
210 onClick={ () => {
211 setSelectedColors( [] );
212 setIsColorOpen( false );
213 } }
214 >
215 { __( 'Clear', 'templates-patterns-collection' ) }
216 </button>
217 ) }
218 </div>
219 </div>
220 ) }
221 <SelectControl
222 className="ob-sort-select"
223 label={ __( 'Sort', 'templates-patterns-collection' ) }
224 hideLabelFromVision
225 __next40pxDefaultSize
226 __nextHasNoMarginBottom
227 value={ searchQuery ? 'recommended' : sortBy }
228 options={ sortOptions }
229 onChange={ setSortBy }
230 disabled={ !! searchQuery }
231 />
232 </div>
233 </div>
234 );
235 };
236
237 export default Filters;
238