Use Codex for typeahead search styles

- Update SearchBox.mustache markup to use codex styles
- Scope old SearchBox markup to LegacySearchBox.mustache
- Add handling for thumbnail and autoexpand search variants
- Adds a 'Search' button to SearchBox.mustache matching the initial non vue search box with the Codex design
- Refactor SearchBox CSS so styles are scoped better

Visual changes:
A "Search" button now appears on page load when it previously only appeared after loading in Vue

Bug: T337966
Change-Id: Ibcffe00292ab4f9f5f9919982d578793cf8594de
This commit is contained in:
bwang 2023-05-24 14:39:52 -05:00 committed by Jon Robson
parent 68239ae344
commit b2705c55f1
10 changed files with 154 additions and 161 deletions

View file

@ -26,6 +26,7 @@ class VectorComponentSearchBox implements VectorComponent {
private $location;
/** @var Config */
private $config;
private const SEARCH_COLLAPSIBLE_CLASS = 'vector-search-box-collapses';
private const SEARCH_SHOW_THUMBNAIL_CLASS = 'vector-search-box-show-thumbnail';
private const SEARCH_AUTO_EXPAND_WIDTH_CLASS = 'vector-search-box-auto-expand-width';
@ -56,61 +57,6 @@ class VectorComponentSearchBox implements VectorComponent {
return $this->location;
}
/**
* Annotates search box with Vector-specific information
*
* @param array $searchBoxData
* @param bool $isCollapsible
* @param bool $isPrimary
* @param string $formId
* @param bool $autoExpandWidth
* @return array modified version of $searchBoxData
*/
private function getSearchData(
array $searchBoxData,
bool $isCollapsible,
bool $isPrimary,
string $formId,
bool $autoExpandWidth
) {
$searchClass = 'vector-search-box-vue ';
if ( $isCollapsible ) {
$searchClass .= ' vector-search-box-collapses ';
}
if ( $this->doesSearchHaveThumbnails() ) {
$searchClass .= ' ' . self::SEARCH_SHOW_THUMBNAIL_CLASS .
( $autoExpandWidth ? ' ' . self::SEARCH_AUTO_EXPAND_WIDTH_CLASS : '' );
}
// Annotate search box with a component class.
$searchBoxData['class'] = trim( $searchClass );
$searchBoxData['is-collapsible'] = $isCollapsible;
$searchBoxData['is-primary'] = $isPrimary;
$searchBoxData['form-id'] = $formId;
$searchBoxData['input-location'] = $this->getSearchBoxInputLocation();
// At lower resolutions the search input is hidden search and only the submit button is shown.
// It should behave like a form submit link (e.g. submit the form with no input value).
// We'll wire this up in a later task T284242.
$collapseIconAttrs = Linker::tooltipAndAccesskeyAttribs( 'search' );
$searchButton = new VectorComponentButton(
$this->localizer->msg( 'search' ),
'search',
'',
'search-toggle',
$collapseIconAttrs,
'quiet',
'default',
true,
Title::newFromText( $searchBoxData['page-title'] )->getLocalURL()
);
$searchBoxData['data-collapsed-search-button'] = $searchButton->getTemplateData();
return $searchBoxData;
}
/**
* @param array $searchBoxData
* @param bool $isCollapsible
@ -145,12 +91,44 @@ class VectorComponentSearchBox implements VectorComponent {
* @inheritDoc
*/
public function getTemplateData(): array {
return $this->getSearchData(
$this->searchBoxData,
$this->isCollapsible,
$this->isPrimary,
$this->formId,
$this->autoExpandWidth
$searchBoxData = $this->searchBoxData;
$isCollapsible = $this->isCollapsible;
$isThumbnail = $this->doesSearchHaveThumbnails();
$isAutoExpand = $isThumbnail && $this->autoExpandWidth;
$isPrimary = $this->isPrimary;
$formId = $this->formId;
$searchClass = 'vector-search-box-vue ';
$searchClass .= $isCollapsible ? ' ' . self::SEARCH_COLLAPSIBLE_CLASS : '';
$searchClass .= $isThumbnail ? ' ' . self::SEARCH_SHOW_THUMBNAIL_CLASS : '';
$searchClass .= $isAutoExpand ? ' ' . self::SEARCH_AUTO_EXPAND_WIDTH_CLASS : '';
// Annotate search box with a component class.
$searchBoxData['class'] = trim( $searchClass );
$searchBoxData['is-collapsible'] = $isCollapsible;
$searchBoxData['is-thumbnail'] = $isThumbnail;
$searchBoxData['is-auto-expand'] = $isAutoExpand;
$searchBoxData['is-primary'] = $isPrimary;
$searchBoxData['form-id'] = $formId;
$searchBoxData['input-location'] = $this->getSearchBoxInputLocation();
// At lower resolutions the search input is hidden search and only the submit button is shown.
// It should behave like a form submit link (e.g. submit the form with no input value).
// We'll wire this up in a later task T284242.
$collapseIconAttrs = Linker::tooltipAndAccesskeyAttribs( 'search' );
$searchButton = new VectorComponentButton(
$this->localizer->msg( 'search' ),
'search',
'',
'search-toggle',
$collapseIconAttrs,
'quiet',
'default',
true,
Title::newFromText( $searchBoxData['page-title'] )->getLocalURL()
);
$searchBoxData['data-collapsed-search-button'] = $searchButton->getTemplateData();
return $searchBoxData;
}
}

View file

@ -0,0 +1,32 @@
{{!
See @typedef SearchData
}}
<div{{#is-primary}} id="p-search"{{/is-primary}} role="search" class="{{class}} vector-search-box">
<h3 {{{html-user-language-attributes}}}>{{msg-search}}</h3>
<form action="{{form-action}}" id="{{form-id}}" class="vector-search-box-form">
<div {{#is-primary}}id="simpleSearch"{{/is-primary}}
class="vector-search-box-inner"
{{#input-location}} data-search-loc="{{.}}"{{/input-location}}>
<input class="vector-search-box-input"
{{#is-primary}}{{{html-input-attributes}}} id="searchInput"{{/is-primary}}
{{^is-primary}}
type="search" name="search"
placeholder="{{msg-searchsuggest-search}}"
{{/is-primary}}
>
<input type="hidden" name="title" value="{{page-title}}">
{{! We construct two buttons (for 'go' and 'fulltext' search modes), but only one will be
visible and actionable at a time (they are overlaid on top of each other in CSS).
* Browsers will use the 'fulltext' one by default (as it's the first in tree-order),
which is desirable when they are unable to show search suggestions (either due to being
broken or having JavaScript turned off).
* The mediawiki.searchSuggest module, after doing tests for the broken browsers, removes
the 'fulltext' button and handles 'fulltext' search itself; this will reveal the 'go'
button and cause it to be used. !}}
<input{{#is-primary}} id="mw-searchButton"{{/is-primary}}
{{{html-button-fulltext-attributes}}} value="{{msg-searchbutton}}">
<input{{#is-primary}} id="searchButton"{{/is-primary}}
{{{html-button-go-attributes}}} value="{{msg-searcharticle}}">
</div>
</form>
</div>

View file

@ -7,38 +7,22 @@
{{>Button}}
{{/data-collapsed-search-button}}
{{/is-collapsible}}
<div>
{{#has-label}}
<h3 {{{html-user-language-attributes}}}>
<label{{#is-primary}} for="searchInput"{{/is-primary}}>{{msg-search}}</label>
</h3>
{{/has-label}}
<form action="{{form-action}}" id="{{form-id}}"
class="vector-search-box-form">
<div {{#is-primary}}id="simpleSearch"{{/is-primary}}
class="vector-search-box-inner"
{{#input-location}} data-search-loc="{{.}}"{{/input-location}}>
<input class="vector-search-box-input"
{{#is-primary}}{{{html-input-attributes}}} id="searchInput"{{/is-primary}}
{{^is-primary}}
type="search" name="search"
placeholder="{{msg-searchsuggest-search}}"
{{/is-primary}}
>
<input type="hidden" name="title" value="{{page-title}}">
{{! We construct two buttons (for 'go' and 'fulltext' search modes), but only one will be
visible and actionable at a time (they are overlaid on top of each other in CSS).
* Browsers will use the 'fulltext' one by default (as it's the first in tree-order),
which is desirable when they are unable to show search suggestions (either due to being
broken or having JavaScript turned off).
* The mediawiki.searchSuggest module, after doing tests for the broken browsers, removes
the 'fulltext' button and handles 'fulltext' search itself; this will reveal the 'go'
button and cause it to be used. !}}
<input{{#is-primary}} id="mw-searchButton"{{/is-primary}}
{{{html-button-fulltext-attributes}}} value="{{msg-searchbutton}}">
<input{{#is-primary}} id="searchButton"{{/is-primary}}
{{{html-button-go-attributes}}} value="{{msg-searcharticle}}">
</div>
</form>
<div class="vector-typeahead-search-container">
<div class="cdx-typeahead-search{{#is-thumbnail}} cdx-typeahead-search--show-thumbnail{{/is-thumbnail}}{{#is-auto-expand}} cdx-typeahead-search--auto-expand-width{{/is-auto-expand}}">
<form action="{{form-action}}" id="{{form-id}}" class="cdx-search-input cdx-search-input--has-end-button">
<div {{#is-primary}}id="simpleSearch"{{/is-primary}} class="cdx-search-input__input-wrapper" {{#input-location}} data-search-loc="{{.}}"{{/input-location}}>
<div class="cdx-text-input cdx-text-input--has-start-icon">
<input
class="cdx-text-input__input"
{{#is-primary}}{{{html-input-attributes}}} id="searchInput"{{/is-primary}}
{{^is-primary}}type="search" name="search" placeholder="{{msg-searchsuggest-search}}"{{/is-primary}}
>
<span class="cdx-text-input__icon cdx-text-input__start-icon"></span>
</div>
<input type="hidden" name="title" value="{{page-title}}">
</div>
<button class="cdx-button cdx-search-input__end-button">{{msg-searchbutton}}</button>
</form>
</div>
</div>
</div>

View file

@ -60,7 +60,7 @@
<div id="right-navigation">
{{#data-views}}{{>LegacyMenu}}{{/data-views}}
{{#data-actions}}{{>LegacyMenu}}{{/data-actions}}
{{#data-search-box}}{{>SearchBox}}{{/data-search-box}}
{{#data-search-box}}{{>LegacySearchBox}}{{/data-search-box}}
</div>
{{/data-portlets}}
</div>

View file

@ -1,6 +1,8 @@
@import 'mediawiki.mixins.less';
@import '../../common/variables.less';
// FIXME: Move entire file into the legacy SearchBox.less after caching
// Defined as `div`.
// Provide extra element for gadgets due to `form` already carrying an `id`.
.vector-search-box-inner {
@ -26,7 +28,6 @@
box-shadow: @box-shadow-inset-small @box-shadow-color-transparent;
// Match Codex.
font-family: inherit;
font-size: @font-size-search-input;
direction: ltr;
transition-property: border-color, box-shadow;
transition-duration: @transition-duration-medium;
@ -93,24 +94,3 @@
background: no-repeat center/unit( 16 / @font-size-browser / @font-size-search-input, em ) url( images/search.svg );
opacity: 0.67;
}
.search-toggle {
// At lower resolutions the search input is hidden and a toggle is shown
display: inline-flex;
float: right;
// Ensures the button has a font size of 16px
font-size: unit( 16 / @font-size-browser, rem );
@media ( min-width: @min-width-desktop ) {
// Override .cdx-button styles
display: none !important; /* stylelint-disable-line declaration-no-important */
}
}
.vector-search-box-collapses > div {
display: none;
@media ( min-width: @min-width-desktop ) {
display: block;
}
}

View file

@ -10,12 +10,14 @@ const
* @return {void}
*/
function initApp( searchBox ) {
const searchForm = searchBox.querySelector( '.vector-search-box-form' ),
// FIXME: Remove searchForm.parentNode after caching
const searchForm = searchBox.querySelector( '.vector-search-box-form, .cdx-search-input' ),
titleInput = /** @type {HTMLInputElement|null} */ (
searchBox.querySelector( 'input[name=title]' )
),
search = /** @type {HTMLInputElement|null} */ ( searchBox.querySelector( 'input[name=search]' ) ),
searchPageTitle = titleInput && titleInput.value;
searchPageTitle = titleInput && titleInput.value,
searchContainer = searchBox.querySelector( '.vector-typeahead-search-container' );
if ( !searchForm || !search || !titleInput ) {
throw new Error( 'Attempted to create Vue search element from an incompatible element.' );
@ -37,7 +39,8 @@ function initApp( searchBox ) {
// Pass additional config from server.
}, config )
)
.mount( searchForm.parentNode );
// FIXME: Remove searchForm.parentNode after caching
.mount( searchContainer || searchForm.parentNode );
}
/**
* @param {Document} document

View file

@ -21,16 +21,26 @@
flex-grow: 1;
}
@media ( min-width: @min-width-desktop-wide ) {
.client-js & .vector-search-box.vector-search-box-auto-expand-width {
// Ensure search box is aligned with content when it autoexpands (i.e. search thumbnails)
margin-left: -@size-search-expand;
.search-toggle {
// At lower resolutions the search input is hidden and a toggle is shown
display: inline-flex;
float: right;
// Ensures the button has a font size of 16px
font-size: unit( 16 / @font-size-browser, rem );
@media ( min-width: @min-width-desktop ) {
// Override .cdx-button styles
display: none !important; /* stylelint-disable-line declaration-no-important */
}
}
@media ( min-width: @min-width-desktop ) {
.vector-search-box {
margin-right: @margin-end-search;
// FIXME: Remove .vector-search-box-collapses > div after caching
.vector-search-box-collapses > div,
.vector-typeahead-search-container {
display: none;
@media ( min-width: @min-width-desktop ) {
display: block;
}
}
@ -44,22 +54,21 @@
display: none;
}
.vector-search-box-collapses > div {
// FIXME: Remove .vector-search-box-collapses > div after caching
.vector-search-box-collapses > div,
.vector-typeahead-search-container {
display: block;
// Override default max width at lower resolutions
max-width: none;
}
.vector-search-box {
// T284242#7206507: Widen the suggestion results to the edge of the search
// button at small resolutions.
position: relative;
margin-right: @margin-end-search;
// Override default max width at lower resolutions
> div {
max-width: none;
}
}
// FIXME: Remove selector after caching
// Make the menu below the search input wider, to match the width of the input+button
// rather than just the width of the input
.cdx-search-input__input-wrapper {

View file

@ -0,0 +1,27 @@
@import '../../common/variables.less';
// Search container
// We have to put those styles outside `.skin-vector-search-vue`,
// as we can't address no-JS modern and Vue enhanced otherwise.
.vector-search-box {
// Use Vector's base font-size, as this is a component outside of `.vector-body`.
// Support IE 9-11, Trident cuts values 2 digits after decimal point.
// `calc` enables to set correct calculation in place again. See T102364.
font-size: @font-size-base--trident-hack;
// If this is a flex item, make sure it grows into available space.
flex-grow: 1;
// FIXME: Remove > div selector after caching
> div,
.vector-typeahead-search-container {
max-width: @max-width-search;
margin-right: @margin-end-search;
}
}
@media ( min-width: @min-width-desktop-wide ) {
.client-js & .vector-search-box.vector-search-box-auto-expand-width {
// Ensure search box is aligned with content when it autoexpands (i.e. search thumbnails)
margin-left: -@size-search-expand;
}
}

View file

@ -1,5 +1,7 @@
@import '../../common/variables.less';
// FIXME: Remove entire file after caching
/**
* Minimal styling for initial no-JS server-rendered
* search form, which gets replaced by Codex on focus.
@ -8,29 +10,6 @@
* ResourceLoader LESS transformation of `calc`.
*/
// Derived from @size-base design token in Codex
@size-base: 32px;
@min-size-search-button: 30px;
@background-size-x-search-button: 20px;
// Search container
// We have to put those styles outside `.skin-vector-search-vue`,
// as we can't address no-JS modern and Vue enhanced otherwise.
.vector-search-box {
// Use Vector's base font-size, as this is a component outside of `.vector-body`.
font-size: @font-size-base;
// Support IE 9-11, Trident cuts values 2 digits after decimal point.
// `calc` enables to set correct calculation in place again. See T102364.
font-size: @font-size-base--trident-hack;
// If this is a flex item, make sure it grows into available space.
flex-grow: 1;
> div {
max-width: @max-width-search;
}
}
// Typeahead search elements
.vector-search-box-vue .vector-search-box-input,
.vector-search-box-vue .searchButton {
@ -41,18 +20,18 @@
.vector-search-box-vue .vector-search-box-input {
height: auto;
// The following is copied directly from Codex.
min-height: @size-base;
min-height: 32px;
line-height: 1.4285714;
padding-top: 4px;
padding-bottom: 4px;
}
.vector-search-box-vue .searchButton {
background-size: @background-size-x-search-button auto;
background-size: 20px auto;
}
// Only apply the following Codex-related rules to clients who have js enabled.
.client-js .vector-search-box-vue {
.client-js .vector-search-box-vue > div:not( .vector-typeahead-search-container ) {
// Derived from @size-search-figure in Codex.
// https://gerrit.wikimedia.org/r/plugins/gitiles/design/codex/+/refs/tags/v0.1.0-alpha.8/packages/codex/src/components/typeahead-search/TypeaheadSearch.vue#676
@size-search-figure: 40px;

View file

@ -29,7 +29,6 @@
@media screen {
// Components
@import './components/Button.less';
@import './components/SearchBoxLoader.less';
@import './components/VueEnhancedSearchBox.less';
@import './components/LanguageDropdown.less';
@import './components/UserLinks.less';
@ -41,6 +40,8 @@
@import './components/PageTitlebar.less';
@import './components/PageToolbar.less';
@import './components/PopupNotification.less';
@import './components/SearchBox.less';
@import './components/SearchBoxLoader.less';
@import './components/Watchstar.less';
@import './components/LimitedWidthToggle.less';
}