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 |