diff --git a/public/class-partnerexpo-core-public.php b/public/class-partnerexpo-core-public.php index 1649e0f..6e070df 100644 --- a/public/class-partnerexpo-core-public.php +++ b/public/class-partnerexpo-core-public.php @@ -47,6 +47,8 @@ class Partnerexpo_Core_Public { public function searchbox_shortcode() { wp_enqueue_style( $this->plugin_name . '-searchbox-css' ); wp_enqueue_script( $this->plugin_name . '-searchbox-js' ); + wp_enqueue_style( $this->plugin_name . '-multiselect-css' ); + wp_enqueue_script( $this->plugin_name . '-multiselect-js' ); ob_start(); include plugin_dir_path( __FILE__ ) . 'partials/partnerexpo-core-public-searchbox.php'; @@ -62,12 +64,44 @@ class Partnerexpo_Core_Public { } function query_partners(WP_REST_Request $request) { + $params = $request->get_query_params(); + + if (isset($params['force_tags']) && isset($params['tags']) && $params['force_tags'] === 'true') { + $params['tags'] = str_replace(',', '+', $params['tags']); + } + $args = [ 'post_type' => 'pexpo_partners', - 'posts_per_page' => 15, + 'posts_per_page' => $params['resultsPerPage'] ?? 10, + 'paged' => $params['page'] ?? 1, 'post_status' => 'publish', + 's' => $params['q'] ?? '', + 'pexpo_tags' => $params['tags'] ?? '', ]; + switch ($params['sort'] ?? 'relevance') { + case 'date_asc': + $args['orderby'] = 'date'; + $args['order'] = 'ASC'; + break; + case 'date_desc': + $args['orderby'] = 'date'; + $args['order'] = 'DESC'; + break; + case 'title_asc': + $args['orderby'] = 'title'; + $args['order'] = 'ASC'; + break; + case 'title_desc': + $args['orderby'] = 'title'; + $args['order'] = 'DESC'; + break; + case 'relevance': + default: + // Default WordPress search sorting + break; + } + $query = new WP_Query($args); $posts = []; @@ -83,6 +117,7 @@ class Partnerexpo_Core_Public { 'date' => date('Y-m-d', strtotime($post->post_date)), 'tag' => $tags ?? null, 'url' => get_permalink($post), + 'order' => $params['sort'] ?? 'relevance', ]; } @@ -96,7 +131,7 @@ class Partnerexpo_Core_Public { * * @since 1.0.0 */ - public function enqueue_styles() { + public function register_styles() { wp_register_style( $this->plugin_name . '-searchbox-css', plugin_dir_url( __FILE__ ) . 'css/searchbox.css', @@ -104,6 +139,14 @@ class Partnerexpo_Core_Public { $this->version, 'all' ); + + wp_register_style( + $this->plugin_name . '-multiselect-css', + plugin_dir_url( __FILE__ ) . 'css/multiselect.css', + [], + $this->version, + 'all' + ); } /** @@ -111,7 +154,7 @@ class Partnerexpo_Core_Public { * * @since 1.0.0 */ - public function enqueue_scripts() { + public function register_scripts() { wp_register_script( $this->plugin_name . '-searchbox-js', plugin_dir_url( __FILE__ ) . 'js/searchbox.js', @@ -119,6 +162,14 @@ class Partnerexpo_Core_Public { $this->version, true ); + + wp_register_script( + $this->plugin_name . '-multiselect-js', + plugin_dir_url( __FILE__ ) . 'js/multiselect.js', + [], + $this->version, + true + ); } diff --git a/public/css/multiselect.css b/public/css/multiselect.css new file mode 100644 index 0000000..b4ce404 --- /dev/null +++ b/public/css/multiselect.css @@ -0,0 +1,206 @@ +:root { + --spacing-smaller: 3px; + --spacing-small: 5px; + --spacing-medium: 7px; + --spacing-large: 12px; + --font-size: 12px; + --font-size-large: 14px; + --font-size-larger: 16px; + --line-height: 16px; + --line-height-larger: 20px; + --primary-color: #40c979; + --text-color-dark: #212529; + --text-color: #585858; + --text-color-light: #1018289e; + --border-color: #bebebe; + --border-color-light: #f1f3f5; + --input-placeholder: #65727e; + --input-background: #e9e9ed; + --input-border: #dee2e6; + --input-border-active: #c1c9d0; + --input-border-invalid: #e44e4e; + --input-outline-invalid: rgba(219, 138, 138, 0.5); + --input-color: #e9e9ed; + --input-disabled: #f7f7f7; + --input-min-height: 45px; + --options-height: 40dvh; + --option-background: #f3f4f7; + --border-radius: 10px; + --icon-size: 12px; + --icon-space: 30px; + --checkbox-size: 16px; + --checkbox-radius: 4px; + --checkbox-border: #ced4da; + --checkbox-background: #fff; + --checkbox-active: #fff; + --checkbox-thickness: 2px; +} +.multi-select { + display: flex; + box-sizing: border-box; + flex-direction: column; + position: relative; + width: 100%; + user-select: none; +} +.multi-select .multi-select-header { + border: 1px solid var(--input-border); + border-radius: var(--border-radius); + padding: var(--spacing-medium) var(--spacing-large); + padding-right: var(--icon-space); + overflow: hidden; + gap: var(--spacing-medium); + min-height: var(--input-min-height); +} +.multi-select .multi-select-header::after { + content: ""; + display: block; + position: absolute; + top: 50%; + right: 15px; + transform: translateY(-50%); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23949ba3' viewBox='0 0 16 16'%3E%3Cpath d='M8 13.1l-8-8 2.1-2.2 5.9 5.9 5.9-5.9 2.1 2.2z'/%3E%3C/svg%3E"); + height: var(--icon-size); + width: var(--icon-size); +} +.multi-select .multi-select-header.multi-select-header-active { + border-color: var(--input-border-active); +} +.multi-select .multi-select-header.multi-select-header-active::after { + transform: translateY(-50%) rotate(180deg); +} +.multi-select .multi-select-header.multi-select-header-active + .multi-select-options { + display: flex; +} +.multi-select .multi-select-header .multi-select-header-placeholder { + color: var(--text-color-light); +} +.multi-select .multi-select-header .multi-select-header-option { + display: inline-flex; + align-items: center; + background-color: var(--option-background); + font-size: var(--font-size-large); + padding: var(--spacing-smaller) var(--spacing-small); + border-radius: var(--border-radius); +} +.multi-select .multi-select-header .multi-select-header-max { + font-size: var(--font-size-large); + color: var(--text-color-light); +} +.multi-select .multi-select-options { + display: none; + box-sizing: border-box; + flex-flow: wrap; + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 999; + margin-top: var(--spacing-small); + padding: var(--spacing-small); + background-color: #fff; + border-radius: var(--border-radius); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + max-height: var(--options-height); + overflow-y: auto; + overflow-x: hidden; +} +.multi-select .multi-select-options::-webkit-scrollbar { + width: 5px; +} +.multi-select .multi-select-options::-webkit-scrollbar-track { + background: #f0f1f3; +} +.multi-select .multi-select-options::-webkit-scrollbar-thumb { + background: #cdcfd1; +} +.multi-select .multi-select-options::-webkit-scrollbar-thumb:hover { + background: #b2b6b9; +} +.multi-select .multi-select-options .multi-select-option, +.multi-select .multi-select-options .multi-select-all { + padding: var(--spacing-large); +} +.multi-select .multi-select-options .multi-select-option .multi-select-option-radio, +.multi-select .multi-select-options .multi-select-all .multi-select-option-radio { + background: var(--checkbox-background); + margin-right: var(--spacing-large); + height: var(--checkbox-size); + width: var(--checkbox-size); + border: 1px solid var(--checkbox-border); + border-radius: var(--checkbox-radius); +} +.multi-select .multi-select-options .multi-select-option .multi-select-option-text, +.multi-select .multi-select-options .multi-select-all .multi-select-option-text { + box-sizing: border-box; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: inherit; + font-size: var(--font-size-larger); + line-height: var(--line-height); +} +.multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-radio, +.multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-radio { + border-color: var(--primary-color); + background-color: var(--primary-color); +} +.multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-radio::after, +.multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-radio::after { + content: ""; + display: block; + width: calc(var(--checkbox-size) / 4); + height: calc(var(--checkbox-size) / 2); + border: solid var(--checkbox-active); + border-width: 0 var(--checkbox-thickness) var(--checkbox-thickness) 0; + transform: rotate(45deg) translate(50%, -25%); +} +.multi-select .multi-select-options .multi-select-option.multi-select-selected .multi-select-option-text, +.multi-select .multi-select-options .multi-select-all.multi-select-selected .multi-select-option-text { + color: var(--text-color-dark); +} +.multi-select .multi-select-options .multi-select-option:hover, .multi-select .multi-select-options .multi-select-option:active, +.multi-select .multi-select-options .multi-select-all:hover, .multi-select .multi-select-options .multi-select-all:active { + background-color: var(--option-background); +} +.multi-select .multi-select-options .multi-select-all { + border-bottom: 1px solid var(--border-color-light); + border-radius: 0; +} +.multi-select .multi-select-options .multi-select-search { + padding: var(--spacing-medium) var(--spacing-large); + border: 1px solid var(--input-border); + border-radius: var(--border-radius); + margin: 10px 10px 5px 10px; + width: 100%; + outline: none; + font-size: var(--font-size-larger); +} +.multi-select .multi-select-options .multi-select-search::placeholder { + color: var(--text-color-light); +} +.multi-select .multi-select-header, +.multi-select .multi-select-option, +.multi-select .multi-select-all { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: center; + border-radius: var(--border-radius); + cursor: pointer; + display: flex; + align-items: center; + width: 100%; + font-size: var(--font-size-larger); + color: var(--text-color-dark); +} +.multi-select.disabled { + opacity: 0.6; + pointer-events: none; + background-color: var(--input-disabled); +} +.multi-select.multi-select-invalid .multi-select-header { + border-color: var(--input-border-invalid); + outline: var(--input-outline-invalid) solid 1px; +} diff --git a/public/css/searchbox.css b/public/css/searchbox.css index fdce544..1e918a8 100644 --- a/public/css/searchbox.css +++ b/public/css/searchbox.css @@ -6,13 +6,13 @@ --panel: #ffffff; --panel2: #f2f4fb; - --stroke: rgba(16, 24, 40, .10); - --stroke2: rgba(16, 24, 40, .14); + --stroke: #1018281a; + --stroke2: #10182824; - --text: rgba(16, 24, 40, .92); - --muted: rgba(16, 24, 40, .62); + --text: #101828eb; + --muted: #1018289e; - --shadow: 0 18px 45px rgba(16,24,40,.12); + --shadow: 0 18px 45px #1018281f; --r: 14px; --cardMin: 240; @@ -21,6 +21,9 @@ --filterW: 170px; --filterWOpen: 360px; + --toggle-bg-color: #4281A4; + --toggle-nub-color: #FF686B; + --topbarH: 64px; /* Basic font reset for the component only */ @@ -369,14 +372,70 @@ } .pexpo-core-filterPanelHeader b { color: var(--text); } -.pexpo-core-jsonBox { +.pexpo-core-filterPanelBody { + display: flex; padding: 10px; - flex: 1 1 auto; + flex-direction: column; overflow:auto; min-height: 0; + gap: 10px; } -.pexpo-core-textarea { +.pexpo-core-toggle { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; +} + +.pexpo-core-toggle input[type="checkbox"] { + display: none; +} + +.pexpo-core-toggle label { + position: relative; + display: block; + width: 50px; + height: 25px; + cursor: pointer; +} + +.pexpo-core-toggle label::before { + content: ''; + display: block; + width: 100%; + height: 100%; + border-radius: 4px; + background: var(--toggle-bg-off); + border: 1.5px solid color-mix(in srgb, var(--toggle-bg-off), black var(--darker)); + box-sizing: border-box; + transition: all 0.2s ease-in; +} + +.pexpo-core-toggle label::after { + content: ''; + display: block; + height: 21px; + width: 21px; + border-radius: 2px; + background: var(--toggle-nub-color); + position: absolute; + top: 2px; + left: 2px; + transition: all 0.2s ease-in; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); +} + +.pexpo-core-toggle input:checked + label::before { + background: var(--toggle-bg-on); + border: 1.5px solid color-mix(in srgb, var(--toggle-bg-on), black var(--darker)); +} + +.pexpo-core-toggle input:checked + label::after { + left: 27px; +} + +/* .pexpo-core-textarea { width: 100%; height: 210px; resize: vertical; @@ -395,7 +454,7 @@ .pexpo-core-textarea:focus { border-color: rgba(44,52,137,.45); box-shadow: 0 0 0 3px rgba(44,52,137,.14); -} +} */ .pexpo-core-hint { margin-top: 8px; diff --git a/public/js/multiselect.js b/public/js/multiselect.js new file mode 100644 index 0000000..befbac4 --- /dev/null +++ b/public/js/multiselect.js @@ -0,0 +1,375 @@ +/* + * Created by David Adams + * https://codeshack.io/multi-select-dropdown-html-javascript/ + * + * Released under the MIT license + */ +class MultiSelect { + + constructor(element, options = {}) { + let defaults = { + searchText: 'Search...', + placeholder: 'Select item(s)', + max: null, + min: null, + disabled: false, + search: true, + selectAll: true, + listAll: true, + closeListOnItemSelect: false, + name: '', + width: '', + height: '', + dropdownWidth: '', + dropdownHeight: '', + data: [], + onChange: function() {}, + onSelect: function() {}, + onUnselect: function() {}, + onMaxReached: function() {} + }; + this.options = Object.assign(defaults, options); + this.selectElement = typeof element === 'string' ? document.querySelector(element) : element; + this.originalSelectElement = this.selectElement.cloneNode(true); + for(const prop in this.selectElement.dataset) { + if (this.options[prop] !== undefined) { + if (typeof this.options[prop] === 'boolean') { + this.options[prop] = this.selectElement.dataset[prop] === 'true'; + } else { + this.options[prop] = this.selectElement.dataset[prop]; + } + } + } + this.name = this.selectElement.getAttribute('name') ? this.selectElement.getAttribute('name') : 'multi-select-' + Math.floor(Math.random() * 1000000); + if (!this.options.data.length) { + let options = this.selectElement.querySelectorAll('option'); + for (let i = 0; i < options.length; i++) { + this.options.data.push({ + value: options[i].value, + text: options[i].innerHTML, + selected: options[i].selected, + html: options[i].getAttribute('data-html') + }); + } + } + this.originalData = JSON.parse(JSON.stringify(this.options.data)); + this.element = this._template(); + this.selectElement.replaceWith(this.element); + this.outsideClickHandler = this._outsideClick.bind(this); + this._updateSelected(); + this._eventHandlers(); + if (this.options.disabled) { + this.disable(); + } + } + + _template() { + let optionsHTML = ''; + for (let i = 0; i < this.data.length; i++) { + const isSelected = this.data[i].selected; + optionsHTML += ` +