mirror of https://github.com/Chocobozzz/PeerTube
Fix search typeahead accessibility
parent
9c215124d1
commit
44eedafed4
|
@ -2,7 +2,7 @@
|
||||||
<input
|
<input
|
||||||
type="search" id="search-video" name="search-video" #searchVideo i18n-placeholder placeholder="Search videos, playlists, channels…"
|
type="search" id="search-video" name="search-video" #searchVideo i18n-placeholder placeholder="Search videos, playlists, channels…"
|
||||||
[(ngModel)]="search" (ngModelChange)="onSearchChange()" (keydown)="handleKey($event)"
|
[(ngModel)]="search" (ngModelChange)="onSearchChange()" (keydown)="handleKey($event)"
|
||||||
autocomplete="off"
|
autocomplete="off" aria-describedby="typeahead-input-help"
|
||||||
>
|
>
|
||||||
|
|
||||||
<button class="border-0 search-button" title="Search" i18n-title (click)="doSearch()">
|
<button class="border-0 search-button" title="Search" i18n-title (click)="doSearch()">
|
||||||
|
@ -17,40 +17,38 @@
|
||||||
role="option" aria-selected="true" tabindex="0"
|
role="option" aria-selected="true" tabindex="0"
|
||||||
(mouseenter)="onSuggestionHover(i)" (click)="onSuggestionClicked(result)" (keyup.enter)="onSuggestionClicked(result)"
|
(mouseenter)="onSuggestionHover(i)" (click)="onSuggestionClicked(result)" (keyup.enter)="onSuggestionClicked(result)"
|
||||||
>
|
>
|
||||||
<my-suggestion [result]="result" [highlight]="search"></my-suggestion>
|
<my-suggestion
|
||||||
|
[result]="result" [highlight]="search"
|
||||||
|
[describedby]="showSearchGlobalHelp() ? 'typeahead-help' : undefined"
|
||||||
|
></my-suggestion>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- suggestion help, not shown until one of the suggestions is selected and specific to that suggestion -->
|
<!-- suggestion help, not shown until one of the suggestions is selected and specific to that suggestion -->
|
||||||
<div *ngIf="showSearchGlobalHelp()" id="typeahead-help" class="overflow-hidden">
|
<div *ngIf="showSearchGlobalHelp()" id="typeahead-suggestion-help" class="overflow-hidden">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="small-title" i18n>GLOBAL SEARCH</div>
|
<div class="small-title mb-2" i18n>GLOBAL SEARCH</div>
|
||||||
<div class="advanced-search-status muted">
|
|
||||||
<span *ngIf="serverConfig" class="me-1" i18n>using {{ serverConfig.search.searchIndex.url }}</span>
|
<span *ngIf="serverConfig" class="muted" i18n>using {{ serverConfig.search.searchIndex.url }}</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="muted" i18n>Results will be augmented with those of a third-party index. Only data necessary to make the query will be sent.</div>
|
<div class="muted" i18n>Results will be augmented with those of a third-party index. Only data necessary to make the query will be sent.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- search instructions, when search input is empty -->
|
<!-- search instructions, when search input is empty -->
|
||||||
<div *ngIf="areInstructionsDisplayed()" id="typeahead-instructions" class="overflow-hidden">
|
<div [hidden]="this.search" id="typeahead-input-help" class="overflow-hidden">
|
||||||
<span class="muted" i18n>Your query will be matched against video names or descriptions, channel names.</span>
|
<span class="muted" i18n>Your query will be matched against video names or descriptions, channel names.</span>
|
||||||
<div class="d-flex justify-content-between mt-3">
|
<div class="mt-3 mb-2 small-title" i18n>ADVANCED SEARCH</div>
|
||||||
<div class="small-title" i18n>ADVANCED SEARCH</div>
|
|
||||||
<div class="advanced-search-status c-help">
|
|
||||||
<span [ngClass]="canSearchAnyURI ? 'text-success' : 'muted'" i18n-title title="Determines whether you can resolve any distant content, or if this instance only allows doing so for instances it follows.">
|
|
||||||
<span *ngIf="canSearchAnyURI()" class="me-1" i18n>any instance</span>
|
|
||||||
<span *ngIf="!canSearchAnyURI()" class="me-1" i18n>only followed instances</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<em>@channel_id@domain</em> <span class="flex-auto muted" i18n>will list the matching channel</span>
|
<em>@channel_id@domain</em> <span class="flex-auto muted" i18n>will list the matching channel</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<em>URL</em> <span class="muted" i18n>will list the matching channel</span>
|
<em>URL</em> <span class="muted" i18n>will list the matching channel</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<em>UUID</em> <span class="muted" i18n>will list the matching video</span>
|
<em>UUID</em> <span class="muted" i18n>will list the matching video</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -37,27 +37,25 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#typeahead-help,
|
#typeahead-suggestion-help,
|
||||||
#typeahead-instructions,
|
#typeahead-input-help,
|
||||||
li.suggestion {
|
.suggestion {
|
||||||
border: 1px solid pvar(--mainBackgroundColor);
|
border: 1px solid pvar(--mainBackgroundColor);
|
||||||
background: pvar(--mainBackgroundColor);
|
background: pvar(--mainBackgroundColor);
|
||||||
transition: .3s ease;
|
transition: .3s ease;
|
||||||
transition-property: box-shadow;
|
transition-property: box-shadow;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
// soft border-radius for the last suggestion and the link inside
|
|
||||||
&:last-of-type {
|
|
||||||
&,
|
|
||||||
::ng-deep a {
|
|
||||||
border-bottom-right-radius: 3px;
|
|
||||||
border-bottom-left-radius: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#typeahead-help,
|
#typeahead-suggestion-help,
|
||||||
#typeahead-instructions {
|
#typeahead-input-help,
|
||||||
|
.suggestion:last-of-type {
|
||||||
|
border-bottom-right-radius: 3px;
|
||||||
|
border-bottom-left-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#typeahead-suggestion-help,
|
||||||
|
#typeahead-input-help {
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
|
@ -110,21 +108,20 @@ li.suggestion {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus,
|
&:focus-within {
|
||||||
::ng-deep &:focus-within {
|
|
||||||
> div:last-child {
|
> div:last-child {
|
||||||
@media screen and (min-width: $mobile-view) {
|
@media screen and (min-width: $mobile-view) {
|
||||||
display: initial !important;
|
display: initial !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#typeahead-help,
|
#typeahead-suggestion-help,
|
||||||
#typeahead-instructions,
|
#typeahead-input-help,
|
||||||
li.suggestion {
|
.suggestion {
|
||||||
box-shadow: rgba(0, 0, 0, 0.2) 0 10px 20px -5px;
|
box-shadow: rgba(0, 0, 0, 0.2) 0 10px 20px -5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep input {
|
input {
|
||||||
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 20px 0;
|
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 20px 0;
|
||||||
border-end-start-radius: 0;
|
border-end-start-radius: 0;
|
||||||
border-end-end-radius: 0;
|
border-end-end-radius: 0;
|
||||||
|
@ -136,21 +133,10 @@ li.suggestion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-search-status {
|
|
||||||
height: max-content;
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
&.c-help {
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-title {
|
.small-title {
|
||||||
@include in-content-small-title;
|
@include in-content-small-title;
|
||||||
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep my-suggestion {
|
my-suggestion {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,10 +70,6 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDes
|
||||||
if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
|
if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
areInstructionsDisplayed () {
|
|
||||||
return !this.search
|
|
||||||
}
|
|
||||||
|
|
||||||
showSearchGlobalHelp () {
|
showSearchGlobalHelp () {
|
||||||
return this.search && this.areSuggestionsOpened && this.keyboardEventsManager?.activeItem?.result?.type === 'search-index'
|
return this.search && this.areSuggestionsOpened && this.keyboardEventsManager?.activeItem?.result?.type === 'search-index'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
<a tabindex="-1" class="d-flex flex-auto flex-items-center p-2" [class.focus-visible]="active">
|
<a
|
||||||
<div class="flex-shrink-0 me-2 text-center">
|
tabindex="-1" class="d-flex flex-auto align-center p-2" [class.focus-visible]="active"
|
||||||
<my-global-icon iconName="search"></my-global-icon>
|
[title]="getTitle()"
|
||||||
</div>
|
[attr.aria-describedby]="describedby"
|
||||||
|
>
|
||||||
<img class="avatar me-2 flex-shrink-0 d-none" alt="" aria-label="Team" src="" width="28" height="28">
|
<my-global-icon class="me-2" iconName="search"></my-global-icon>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex-auto overflow-hidden text-start no-wrap css-truncate css-truncate-target"
|
class="flex-auto overflow-hidden no-wrap d-flex align-center"
|
||||||
[attr.aria-label]="result.text"
|
[attr.aria-label]="result.text"
|
||||||
>
|
>
|
||||||
{{ result.text }}
|
{{ result.text }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border rounded flex-shrink-0 px-1 bg-gray text-gray-light ms-1 f6">
|
<div class="result-type border rounded flex-shrink-0 px-1 ms-1 f6">
|
||||||
<span *ngIf="result.type === 'search-instance'" i18n>In this instance's network</span>
|
<span *ngIf="result.type === 'search-instance'" i18n>In this instance's network</span>
|
||||||
<span *ngIf="result.type === 'search-index'" i18n>In the vidiverse</span>
|
<span *ngIf="result.type === 'search-index'" i18n>In the vidiverse</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,11 +17,8 @@ a {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-gray {
|
.result-type {
|
||||||
background-color: pvar(--mainBackgroundColor);
|
background-color: pvar(--mainBackgroundColor);
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-light {
|
|
||||||
color: pvar(--mainForegroundColor);
|
color: pvar(--mainForegroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +27,5 @@ my-global-icon {
|
||||||
|
|
||||||
width: 17px;
|
width: 17px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -2px;
|
top: -1px;
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,16 @@ export type SuggestionPayloadType = 'search-instance' | 'search-index'
|
||||||
export class SuggestionComponent implements OnInit, ListKeyManagerOption {
|
export class SuggestionComponent implements OnInit, ListKeyManagerOption {
|
||||||
@Input() result: SuggestionPayload
|
@Input() result: SuggestionPayload
|
||||||
@Input() highlight: string
|
@Input() highlight: string
|
||||||
|
@Input() describedby: string
|
||||||
|
|
||||||
disabled = false
|
disabled = false
|
||||||
active = false
|
active = false
|
||||||
|
|
||||||
getLabel () {
|
getTitle () {
|
||||||
return this.result.text
|
if (this.result.type === 'search-instance') return $localize`Search "${this.result.text}" in this instance's network`
|
||||||
|
if (this.result.type === 'search-index') return $localize`Search "${this.result.text}" in the vidiverse`
|
||||||
|
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
|
|
Loading…
Reference in New Issue