V16: Chore: Search modal code sweep (#19061)

* Removes trailing whitespace from manifest names

* Refactored default `umb-search-result-item` element

- Reverted `elementName` constant
- Replaced fallback hashtag inline SVG with "icon-shape-hexagon"
- Added "extra" div (for UI consistency)
- Reworked CSS rules

* Search modal: replaced inline icons

* Search modal: refactored markup

* Search modal: removed font-size

for consistency with the rest of the backoffice.
This commit is contained in:
Lee Kelleher
2025-04-17 08:24:32 +01:00
committed by GitHub
parent bb41647d91
commit aa89eed3aa
10 changed files with 103 additions and 118 deletions

View File

@@ -12,7 +12,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Data Type Search Result Item ',
name: 'Data Type Search Result Item',
alias: 'Umb.SearchResultItem.DataType',
type: 'searchResultItem',
forEntityTypes: [UMB_DATA_TYPE_ENTITY_TYPE],

View File

@@ -13,7 +13,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Dictionary Search Result Item ',
name: 'Dictionary Search Result Item',
alias: 'Umb.SearchResultItem.Dictionary',
type: 'searchResultItem',
forEntityTypes: [UMB_DICTIONARY_ENTITY_TYPE],

View File

@@ -13,7 +13,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Document Search Result Item ',
name: 'Document Search Result Item',
alias: 'Umb.SearchResultItem.Document',
type: 'searchResultItem',
element: () => import('./document-search-result-item.element.js'),

View File

@@ -12,7 +12,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Media Type Search Result Item ',
name: 'Media Type Search Result Item',
alias: 'Umb.SearchResultItem.MediaType',
type: 'searchResultItem',
forEntityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE],

View File

@@ -13,7 +13,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Media Search Result Item ',
name: 'Media Search Result Item',
alias: 'Umb.SearchResultItem.Media',
type: 'searchResultItem',
forEntityTypes: [UMB_MEDIA_ENTITY_TYPE],

View File

@@ -12,7 +12,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Member Type Search Result Item ',
name: 'Member Type Search Result Item',
alias: 'Umb.SearchResultItem.MemberType',
type: 'searchResultItem',
forEntityTypes: [UMB_MEMBER_TYPE_ENTITY_TYPE],

View File

@@ -13,7 +13,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Member Search Result Item ',
name: 'Member Search Result Item',
alias: 'Umb.SearchResultItem.Member',
type: 'searchResultItem',
forEntityTypes: [UMB_MEMBER_ENTITY_TYPE],

View File

@@ -1,5 +1,5 @@
import type { UmbSearchProvider, UmbSearchResultItemModel } from '../types.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { ManifestSearchResultItem } from '../extensions/types.js';
import {
css,
html,
@@ -9,14 +9,15 @@ import {
query,
state,
property,
when,
} from '@umbraco-cms/backoffice/external/lit';
import { UmbExtensionsManifestInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbExtensionsManifestInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
import '../search-result/search-result-item.element.js';
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
import type { ManifestSearchResultItem } from '../extensions/types.js';
type SearchProvider = {
name: string;
@@ -282,48 +283,62 @@ export class UmbSearchModalElement extends UmbLitElement {
override render() {
return html`
<div id="top">
${this.#renderSearchIcon()}
<div id="search-icon">
${when(
this._loading,
() => html`<uui-loader-circle></uui-loader-circle>`,
() => html`<uui-icon name="search"></uui-icon>`,
)}
</div>
<div id="input-wrapper">
<div id="input-wrapper-fake-cursor" aria-hidden="true"></div>
<input
type="text"
autocomplete="off"
placeholder=${this.localize.term('placeholders_search')}
value=${this._search}
@input=${this.#onSearchChange}
@blur=${() => this.#setShowFakeCursor(true)}
@focus=${() => this.#setShowFakeCursor(false)}
type="text"
placeholder=${this.localize.term('placeholders_search')}
autocomplete="off" />
@focus=${() => this.#setShowFakeCursor(false)} />
</div>
</div>
${this.#renderSearchTags()}
${this._search
? html`<div id="main">${this._searchResults.length > 0 ? this.#renderResults() : this.#renderNoResults()}</div>`
: this.#renderNavigationTips()}
${when(
this._search,
() => html`
<uui-scroll-container>
<div id="main">
${when(
this._searchResults.length > 0,
() => this.#renderResults(),
() => this.#renderNoResults(),
)}
</div>
</uui-scroll-container>
`,
() => this.#renderNavigationTips(),
)}
`;
}
#renderSearchIcon() {
return html` <div id="search-icon">
${this._loading ? html`<uui-loader-circle></uui-loader-circle>` : html`<uui-icon name="search"></uui-icon>`}
</div>`;
}
#renderSearchTags() {
return html`<div id="search-providers">
${repeat(
this._searchProviders,
(searchProvider) => searchProvider,
(searchProvider) =>
html`<button
data-provider-alias=${searchProvider.alias}
@click=${() => this.#setCurrentProvider(searchProvider)}
@keydown=${() => ''}
class="search-provider ${this._currentProvider?.alias === searchProvider.alias ? 'active' : ''}">
${searchProvider.name}
</button>`,
)}
</div> `;
return html`
<div id="search-providers">
${repeat(
this._searchProviders,
(searchProvider) => searchProvider.alias,
(searchProvider) => html`
<button
class="search-provider ${this._currentProvider?.alias === searchProvider.alias ? 'active' : ''}"
data-provider-alias=${searchProvider.alias}
@click=${() => this.#setCurrentProvider(searchProvider)}
@keydown=${() => ''}>
${searchProvider.name}
</button>
`,
)}
</div>
`;
}
#renderResults() {
@@ -337,9 +352,9 @@ export class UmbSearchModalElement extends UmbLitElement {
#renderResultItem(item: UmbSearchResultItemModel, index: number) {
return html`
<a
href=${item.href}
data-item-index=${index}
class="search-item"
data-item-index=${index}
href=${item.href}
@click=${this.#closeModal}
@keydown=${this.#closeModal}>
<umb-extension-slot
@@ -352,7 +367,8 @@ export class UmbSearchModalElement extends UmbLitElement {
}
#renderNoResults() {
return this._loading ? nothing : html`<div id="no-results">${this.localize.term('general_searchNoResult')}</div>`;
if (this._loading) return nothing;
return html`<div id="no-results">${this.localize.term('general_searchNoResult')}</div>`;
}
#renderNavigationTips() {
@@ -360,34 +376,10 @@ export class UmbSearchModalElement extends UmbLitElement {
<div class="navigation-tips-key" style="grid-column: span 2;">Tab</div>
<span>${this.localize.term('globalSearch_navigateSearchProviders')}</span>
<div class="navigation-tips-key">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path d="m5 12 7-7 7 7" />
<path d="M12 19V5" />
</svg>
<uui-icon name="icon-arrow-up"></uui-icon>
</div>
<div class="navigation-tips-key">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<path d="M12 5v14" />
<path d="m19 12-7 7-7-7" />
</svg>
<uui-icon name="icon-arrow-down"></uui-icon>
</div>
<span>${this.localize.term('globalSearch_navigateSearchResults')}</span>
</div>`;
@@ -405,9 +397,9 @@ export class UmbSearchModalElement extends UmbLitElement {
background-color: var(--uui-color-surface);
box-sizing: border-box;
color: var(--uui-color-text);
font-size: 1rem;
padding-bottom: var(--uui-size-space-2);
}
#navigation-tips {
display: grid;
grid-template-columns: 50px 50px auto;
@@ -418,6 +410,7 @@ export class UmbSearchModalElement extends UmbLitElement {
margin-top: var(--uui-size-layout-3);
margin-inline: auto;
}
.navigation-tips-key {
display: flex;
align-items: center;
@@ -428,21 +421,24 @@ export class UmbSearchModalElement extends UmbLitElement {
font-size: 0.9rem;
font-weight: bold;
}
#navigation-tips .navigation-tips-key + span {
margin-left: var(--uui-size-space-2);
}
#top {
background-color: var(--uui-color-surface);
display: flex;
height: 48px;
flex-shrink: 0;
}
#main {
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
}
#search-providers {
display: flex;
flex-wrap: wrap;
@@ -450,6 +446,7 @@ export class UmbSearchModalElement extends UmbLitElement {
padding: 0 var(--uui-size-space-5);
padding-bottom: var(--uui-size-space-2);
}
.search-provider {
padding: var(--uui-size-space-3) var(--uui-size-space-4);
background: var(--uui-color-surface-alt);
@@ -460,28 +457,34 @@ export class UmbSearchModalElement extends UmbLitElement {
cursor: pointer;
border: 2px solid transparent;
}
.search-provider:hover {
background: var(--uui-color-surface-emphasis);
color: var(--uui-color-interactive-emphasis);
}
.search-provider.active {
background: var(--uui-color-focus);
color: var(--uui-color-selected-contrast);
border-color: transparent;
}
.search-provider.active:focus {
outline-offset: -4px;
outline-color: var(--uui-color-focus);
}
input {
all: unset;
height: 100%;
width: 100%;
}
#input-wrapper {
width: 100%;
position: relative;
}
#input-wrapper-fake-cursor {
position: absolute;
left: 0;
@@ -493,6 +496,7 @@ export class UmbSearchModalElement extends UmbLitElement {
bottom: 14px;
animation: blink-animation 1s infinite;
}
@keyframes blink-animation {
0%,
50% {
@@ -503,11 +507,13 @@ export class UmbSearchModalElement extends UmbLitElement {
border-color: transparent;
}
}
button {
font-family: unset;
font-size: unset;
cursor: pointer;
}
#search-icon {
display: flex;
align-items: center;
@@ -515,6 +521,7 @@ export class UmbSearchModalElement extends UmbLitElement {
aspect-ratio: 1;
height: 100%;
}
#no-results {
display: flex;
flex-direction: column;
@@ -526,21 +533,25 @@ export class UmbSearchModalElement extends UmbLitElement {
color: var(--uui-color-text-alt);
margin: var(--uui-size-space-5) 0;
}
.search-item {
color: var(--uui-color-text);
text-decoration: none;
outline-offset: -3px;
display: flex;
}
.search-item:hover {
background: var(--uui-color-surface-emphasis);
color: var(--uui-color-interactive-emphasis);
}
.search-item:focus {
outline: 2px solid var(--uui-color-interactive-emphasis);
border-radius: 6px;
outline-offset: -4px;
}
.search-item.active:not(:focus-within) {
outline: 2px solid var(--uui-color-border);
border-radius: 6px;

View File

@@ -1,67 +1,41 @@
import type { UmbSearchResultItemModel } from '../types.js';
import { css, customElement, html, nothing, property, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
const elementName = 'umb-search-result-item';
@customElement(elementName)
@customElement('umb-search-result-item')
export class UmbSearchResultItemElement extends UmbLitElement {
@property({ type: Object })
item?: UmbSearchResultItemModel;
override render() {
if (!this.item) return nothing;
return html`
<span class="item-icon">
${this.item.icon ? html`<umb-icon name="${this.item.icon}"></umb-icon>` : this.#renderHashTag()}
</span>
<span class="item-name"> ${this.item.name} </span>
`;
}
#renderHashTag() {
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M7.784 14l.42-4H4V8h4.415l.525-5h2.011l-.525 5h3.989l.525-5h2.011l-.525 5H20v2h-3.784l-.42 4H20v2h-4.415l-.525 5h-2.011l.525-5H9.585l-.525 5H7.049l.525-5H4v-2h3.784zm2.011 0h3.99l.42-4h-3.99l-.42 4z" />
</svg>
${when(
this.item.icon,
(icon) => html`<umb-icon name=${icon}></umb-icon>`,
() => html`<uui-icon name="icon-shape-hexagon"></uui-icon>`,
)}
<span>${this.item.name}</span>
<div class="extra"></div>
`;
}
static override styles = [
UmbTextStyles,
css`
:host {
padding: var(--uui-size-space-3) var(--uui-size-space-5);
border-radius: var(--uui-border-radius);
display: grid;
grid-template-columns: var(--uui-size-space-6) 1fr var(--uui-size-space-5);
align-items: center;
width: 100%;
outline-offset: -3px;
}
.item-icon {
margin-bottom: auto;
margin-top: 5px;
}
.item-icon {
opacity: 0.4;
}
.item-name {
padding: var(--uui-size-space-3) var(--uui-size-space-5);
display: flex;
flex-direction: column;
}
.item-icon > * {
height: 1rem;
display: flex;
width: min-content;
}
a {
text-decoration: none;
color: inherit;
gap: var(--uui-size-space-3);
align-items: center;
width: 100%;
> span {
flex: 1;
}
}
`,
];
@@ -71,6 +45,6 @@ export { UmbSearchResultItemElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbSearchResultItemElement;
'umb-search-result-item': UmbSearchResultItemElement;
}
}

View File

@@ -12,7 +12,7 @@ export const manifests: Array<UmbExtensionManifest> = [
},
},
{
name: 'Template Search Result Item ',
name: 'Template Search Result Item',
alias: 'Umb.SearchResultItem.Template',
type: 'searchResultItem',
forEntityTypes: [UMB_TEMPLATE_ENTITY_TYPE],