feat(search): add empty state to typeahead

This is a barebone initial implementation, more work will come to it
This commit is contained in:
alistair3149 2022-12-06 12:01:47 -05:00
parent 501286a15d
commit 9bf737f720
No known key found for this signature in database
4 changed files with 118 additions and 46 deletions

View file

@ -15,10 +15,41 @@
// Well this won't be loaded before .citizen-animation-ready anyways
.citizen-card-transition();
&__placeholder {
.citizen-typeahead {
&__content {
flex-direction: column;
padding: var( --space-xl ) 0;
text-align: center;
}
&__thumbnail {
margin-bottom: var( --space-sm );
img,
source {
object-fit: contain;
}
}
}
}
&__item {
&--active {
background-color: var( --background-color-primary--hover );
}
.citizen-typeahead {
&__thumbnail {
margin-right: var( --space-sm );
background-color: #eaecf0;
img,
source {
object-fit: cover;
}
}
}
}
&__content {
@ -31,13 +62,15 @@
&__thumbnail {
overflow: hidden;
width: 100%;
max-width: 70px;
height: 60px;
border-radius: var( --border-radius--medium );
background-color: #eaecf0;
img,
source {
object-fit: cover;
width: inherit;
height: inherit;
}
}
@ -94,16 +127,9 @@
text-overflow: ellipsis;
}
picture {
width: 100%;
max-width: 70px;
margin-right: 0.75rem;
img,
source {
width: inherit;
height: inherit;
}
&__actions {
position: absolute;
right: var( --space-sm );
}
&__keyboard {
@ -116,9 +142,23 @@
&--expanded {
.citizen-card-show( false );
}
&--hasQuery {
.citizen-typeahead {
&__placeholder {
display: none;
}
}
#citizen-typeahead-fulltext {
display: block;
}
}
}
#citizen-typeahead-footer {
#citizen-typeahead-fulltext {
display: none;
.citizen-typeahead {
&__content {
padding: var( --space-md ) 0;
@ -126,8 +166,9 @@
}
&__thumbnail {
width: 70px; // Sync with thumbnail
height: var( --size-icon );
background: transparent;
background-color: transparent;
img {
object-fit: contain;

View file

@ -1,21 +1,22 @@
{{!
string msg-citizen-search-fulltext The label on the fulltext search in the typeahead.
string msg-citizen-search-toggle Search toggle label
}}
<ol id="searchform-suggestions" class="citizen-typeahead" role="listbox">
<li role="option" id="citizen-typeahead-footer" class="citizen-typeahead__item">
<a href="" class="citizen-typeahead__content">
{{! Empty state }}
<li class="citizen-typeahead__placeholder">
<div class="citizen-typeahead__content">
<picture class="citizen-typeahead__thumbnail">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+DQoJPHBhdGggZmlsbD0iIzM2YyIgZD0iTTcuNSAxM2MzLjA0IDAgNS41LTIuNDYgNS41LTUuNVMxMC41NCAyIDcuNSAyIDIgNC40NiAyIDcuNSA0LjQ2IDEzIDcuNSAxM3ptNC41NS40NkE3LjQzMiA3LjQzMiAwIDAgMSA3LjUgMTVDMy4zNiAxNSAwIDExLjY0IDAgNy41UzMuMzYgMCA3LjUgMEMxMS42NCAwIDE1IDMuMzYgMTUgNy41YzAgMS43MS0uNTcgMy4yOS0xLjU0IDQuNTVsNi40OSA2LjQ5LTEuNDEgMS40MS02LjQ5LTYuNDl6Ii8+DQoJPHBhdGggZmlsbD0iIzM2YyIgZD0iTTQgNWg3djFINHptMCAyaDd2MUg0em0wIDJoNS40NDR2MUg0eiIvPg0KPC9zdmc+DQo="/>
</picture>
<div class="citizen-typeahead__text">
<div class="citizen-typeahead__description">{{{msg-citizen-search-fulltext}}}</div>
<div class="citizen-typeahead__title">{{msg-searchsuggest-search}}</div>
<div class="citizen-typeahead__description">{{msg-citizen-search-fulltext-empty}}</div>
</div>
<div class="citizen-typeahead__actions">
<div class="citizen-typeahead__keyboard">↵</div>
</div>
</a>
</div>
</li>
{{! Template }}
<template id="citizen-typeahead-template">
<li role="option" class="citizen-typeahead__item">
<a href="" class="citizen-typeahead__content">

View file

@ -124,7 +124,7 @@ function bindMouseHoverEvent( element ) {
*/
function clearSuggestions() {
const typeaheadItems = typeahead.children,
nonSuggestionCount = 2;
nonSuggestionCount = 3;
if ( typeaheadItems.length > nonSuggestionCount ) {
// Splice would be cleaner but it is slower (?)
@ -210,7 +210,8 @@ function getSuggestions( searchQuery ) {
thumbnail: result.thumbnail ?? '',
title: highlightTitle( result.title ),
label: getRedirectLabel( result.title, result.matchedTitle ),
description: result.description
// Just to be safe, not sure if the default API is HTML escaped
description: mw.html.escape( result.description )
} );
fragment.append( suggestion );
@ -270,25 +271,33 @@ function getMenuItem( data ) {
}
const
item = template.content.cloneNode( true ),
link = item.querySelector( '.' + PREFIX + '__content' ),
thumbnail = item.querySelector( '.' + PREFIX + '__thumbnail img' ),
title = item.querySelector( '.' + PREFIX + '__title' ),
label = item.querySelector( '.' + PREFIX + '__label' ),
description = item.querySelector( '.' + PREFIX + '__description' );
fragment = template.content.cloneNode( true ),
item = fragment.querySelector( '.' + PREFIX + '__item' ),
title = fragment.querySelector( '.' + PREFIX + '__title' ),
label = fragment.querySelector( '.' + PREFIX + '__label' ),
description = fragment.querySelector( '.' + PREFIX + '__description' );
if ( data.id ) {
item.setAttribute( 'id', data.id );
}
if ( data.link ) {
const link = fragment.querySelector( '.' + PREFIX + '__content' );
link.setAttribute( 'href', data.link );
}
if ( data.icon ) {
// FIXME: This is temporary, we need to replace picture elements with background-image because of a11y concern
const thumbnailContainer = fragment.querySelector( '.' + PREFIX + '__thumbnail' );
thumbnailContainer.classList.add( 'citizen-ui-icon', 'mw-ui-icon-wikimedia-' + data.icon );
}
if ( data.thumbnail ) {
const thumbnail = fragment.querySelector( '.' + PREFIX + '__thumbnail img' );
thumbnail.setAttribute( 'src', data.thumbnail );
}
title.innerHTML = data.title ?? '';
label.innerHTML = data.label ?? '';
// Description only contains text
description.textContent = data.description ?? '';
description.innerHTML = data.description ?? '';
return item;
return fragment;
}
/**
@ -298,21 +307,40 @@ function getMenuItem( data ) {
* @return {void}
*/
function updateTypeahead( messages ) {
const searchQuery = searchInput.value,
footer = document.getElementById( PREFIX + '-footer' ),
footerLink = footer.querySelector( '.' + PREFIX + '__content' ),
footerText = footer.querySelector( '.' + PREFIX + '__description' ),
fullTextUrl = config.wgScriptPath + '/index.php?title=Special:Search&fulltext=1&search=';
const
searchQuery = searchInput.value,
queryClass = PREFIX + '--hasQuery';
const updateFullTextSearchItem = () => {
const
// Should this be handled differently since it is escaped a few times?
query = mw.html.escape( searchQuery ),
fulltextId = PREFIX + '-fulltext',
fulltextEl = document.getElementById( fulltextId ),
fulltextText = messages.fulltext + ' <strong>' + query + '</strong>';
const item = getMenuItem( {
icon: 'articleSearch',
id: fulltextId,
link: config.wgScriptPath + '/index.php?title=Special:Search&fulltext=1&search=' + query,
description: fulltextText
} );
// Update existing element instead of creating a new one
if ( fulltextEl ) {
const description = fulltextEl.querySelector( '.' + PREFIX + '__description' );
description.innerHTML = fulltextText;
} else {
typeahead.prepend( item );
}
};
if ( searchQuery.length > 0 ) {
const footerQuery = mw.html.escape( searchQuery );
footerText.innerHTML = messages.fulltext + ' <strong>' + footerQuery + '</strong>';
footerQuery.textContent = searchQuery;
footerLink.setAttribute( 'href', fullTextUrl + searchQuery );
typeahead.classList.add( queryClass );
updateFullTextSearchItem();
getSuggestions( searchQuery );
} else {
footerText.textContent = messages.empty;
footerLink.setAttribute( 'href', fullTextUrl );
typeahead.classList.remove( queryClass );
clearSuggestions();
}
}
@ -326,7 +354,6 @@ function initTypeahead( searchForm, input ) {
const
expandedClass = 'citizen-typeahead--expanded',
messages = {
empty: mw.message( 'citizen-search-fulltext-empty' ).text(),
fulltext: mw.message( 'citizen-search-fulltext' ).text()
},
template = mw.template.get(
@ -334,7 +361,8 @@ function initTypeahead( searchForm, input ) {
'resources/skins.citizen.search/templates/typeahead.mustache'
),
data = {
'msg-citizen-search-fulltext': messages.empty
'msg-searchsuggest-search': mw.message( 'searchsuggest-search' ).text(),
'msg-citizen-search-fulltext-empty': mw.message( 'citizen-search-fulltext-empty' ).text()
};
const onBlur = ( event ) => {

View file

@ -216,7 +216,8 @@
"messages": [
"citizen-search-fulltext",
"citizen-search-fulltext-empty",
"search-redirect"
"search-redirect",
"searchsuggest-search"
],
"targets": [
"desktop",
@ -279,6 +280,7 @@
"icons": [
"article",
"articleRedirect",
"articleSearch",
"block",
"collapse",
"database",