diff --git a/packages/components/username/username.en.mdx b/packages/components/username/username.en.mdx index 3caa29567d..8dec786b9c 100644 --- a/packages/components/username/username.en.mdx +++ b/packages/components/username/username.en.mdx @@ -35,7 +35,7 @@ The component can be conveniently used inside links. To visually match the link To filter a list of users by the displayed name, call `kbqInjectUsernameFormatter()` in an injection context and use the function it returns — it resolves `KBQ_PROFILE_MAPPING` where you call it, so a scoped mapping produces the same string the component renders. -The matched fragment is easy to highlight in a custom template with the `kbqHighlightBackground` pipe. +The matched fragment is easy to highlight in a custom template with the `kbqHighlightBackground` pipe. The example puts the list in a select with search and renders the selected user in its trigger; the toggle switches the trigger to `isCompact`. diff --git a/packages/components/username/username.ru.mdx b/packages/components/username/username.ru.mdx index 2f80c5cce6..d9751214ff 100644 --- a/packages/components/username/username.ru.mdx +++ b/packages/components/username/username.ru.mdx @@ -33,7 +33,7 @@ Чтобы фильтровать список пользователей по отображаемому имени, вызовите `kbqInjectUsernameFormatter()` в контексте внедрения и примените возвращённую функцию. Она разрешает `KBQ_PROFILE_MAPPING` там, где вы её вызвали, поэтому с локальным маппингом вернёт ту же строку, что отрисовывает компонент. -Найденный фрагмент подсветите в пользовательском шаблоне с помощью пайпа `kbqHighlightBackground`. +Найденный фрагмент подсветите в пользовательском шаблоне с помощью пайпа `kbqHighlightBackground`. В примере список вынесен в select с поиском, а выбранный пользователь выводится в его триггере; переключатель включает в триггере режим `isCompact`. diff --git a/packages/docs-examples/components/username/username-filter-bar-option/username-filter-bar-option-example.ts b/packages/docs-examples/components/username/username-filter-bar-option/username-filter-bar-option-example.ts index 79029ce285..ed34167d1b 100644 --- a/packages/docs-examples/components/username/username-filter-bar-option/username-filter-bar-option-example.ts +++ b/packages/docs-examples/components/username/username-filter-bar-option/username-filter-bar-option-example.ts @@ -12,7 +12,8 @@ const USERS: KbqUserInfo[] = [ { firstName: 'Maxwell', middleName: 'Alan', lastName: 'Root', login: 'mroot', site: 'corp' }, { firstName: 'Alice', middleName: 'Marie', lastName: 'Stone', login: 'astone' }, { firstName: 'Robert', lastName: 'Green', login: 'rgreen', site: 'dev' }, - { firstName: 'Elena', middleName: 'Vera', lastName: 'Fox', login: 'efox' } + { firstName: 'Elena', middleName: 'Vera', lastName: 'Fox', login: 'efox' }, + { firstName: 'Madonna', login: 'madonna', site: 'corp' } ]; /** @@ -29,23 +30,34 @@ const USERS: KbqUserInfo[] = [ - @let searchText = pipe.searchControl.value; + @let keywords = splitSearch(pipe.searchControl.value); @let fullName = option.value | kbqUsername; - + @if (option.value?.login) { - + + + @if (option.value.site) { + + } + } `, + styles: ` + .kbq-username__secondary { + display: inline-flex; + gap: var(--kbq-size-xxs); + } + `, changeDetection: ChangeDetectionStrategy.OnPush }) export class UsernameFilterBarOptionExample implements AfterViewInit { @@ -73,6 +85,11 @@ export class UsernameFilterBarOptionExample implements AfterViewInit { pipeTemplates: KbqPipeTemplate[] = []; + // The name, the login and the site are rendered in separate elements, so each word of the query is highlighted on its own. + protected splitSearch(text: string | null): string[] { + return (text ?? '').split(/\s+/).filter(Boolean); + } + ngAfterViewInit(): void { this.pipeTemplates = [ { diff --git a/packages/docs-examples/components/username/username-search/username-search-example.ts b/packages/docs-examples/components/username/username-search/username-search-example.ts index 92d7413611..f3c3016645 100644 --- a/packages/docs-examples/components/username/username-search/username-search-example.ts +++ b/packages/docs-examples/components/username/username-search/username-search-example.ts @@ -1,10 +1,11 @@ -import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { KbqHighlightBackgroundPipe } from '@koobiq/components/core'; -import { KbqFormFieldModule } from '@koobiq/components/form-field'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { createSearchPredicate, KbqHighlightModule, tokenizeSearchQuery } from '@koobiq/components/core'; import { KbqIconModule } from '@koobiq/components/icon'; import { KbqInputModule } from '@koobiq/components/input'; +import { KbqSelectModule, kbqSelectOptionsProvider } from '@koobiq/components/select'; +import { KbqToggleModule } from '@koobiq/components/toggle'; import { kbqBuildUsernameText, kbqInjectUsernameFormatter, @@ -19,50 +20,96 @@ import { startWith } from 'rxjs'; @Component({ selector: 'username-search-example', imports: [ + FormsModule, ReactiveFormsModule, - KbqFormFieldModule, + KbqSelectModule, KbqInputModule, - KbqUsernameModule, KbqIconModule, - KbqHighlightBackgroundPipe + KbqToggleModule, + KbqUsernameModule, + KbqHighlightModule ], template: ` + Compact trigger + - - - - + + + @if (selectedUser(); as user) { + + } + + + + + + + + +
Nothing found
+ + @for (user of filteredUsers(); track user) { + + + + @let fullName = user | kbqUsername; -
- @for (user of filteredUsers(); track user) { - - - @let fullName = user | kbqUsername; - + @if (isCompact()) { + + + @if (user.site) { + + } + + } @else { + - @if (user.login) { - - } - - - } @empty { - Nothing found - } -
+ @if (user.login) { + + + @if (user.site) { + + } + + } + } +
+
+
+ } +
+ `, styles: ` - .example__users-list { - display: flex; - flex-direction: column; - gap: var(--kbq-size-s); + kbq-option .kbq-username__primary, + .kbq-username__secondary { + display: inline-flex; + gap: var(--kbq-size-xxs); + } + + .kbq-form-field-type-select { + width: 320px; } `, + providers: [ + kbqSelectOptionsProvider({ searchMinOptionsThreshold: 0 }) + ], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'layout-column layout-gap-m layout-padding-m' @@ -71,6 +118,8 @@ import { startWith } from 'rxjs'; export class UsernameSearchExample { private readonly formatUsername = kbqInjectUsernameFormatter(); + protected readonly isCompact = signal(false); + protected readonly selectedUser = signal(null); protected readonly searchControl = new FormControl('', { nonNullable: true }); private readonly searchText = toSignal(this.searchControl.valueChanges.pipe(startWith('')), { initialValue: '' }); @@ -79,21 +128,23 @@ export class UsernameSearchExample { { firstName: 'Maxwell', middleName: 'Alan', lastName: 'Root', login: 'mroot', site: 'corp' }, { firstName: 'Alice', middleName: 'Marie', lastName: 'Stone', login: 'astone' }, { firstName: 'Robert', lastName: 'Green', login: 'rgreen', site: 'dev' }, - { firstName: 'Elena', middleName: 'Vera', lastName: 'Fox', login: 'efox' } + { firstName: 'Elena', middleName: 'Vera', lastName: 'Fox', login: 'efox' }, + { firstName: 'Madonna', login: 'madonna', site: 'corp' } ]; - protected readonly filteredUsers = computed(() => { - const query = (this.searchText() ?? '').toLowerCase().trim(); + // The name, the login and the site are rendered in separate elements, so each word of the query is highlighted on its own. + protected readonly keywords = computed(() => tokenizeSearchQuery(this.searchText())); - if (!query) return this.users; + protected readonly filteredUsers = computed(() => { + const matches = createSearchPredicate(this.searchText()); return this.users.filter((user) => - kbqBuildUsernameText( - { name: this.formatUsername(user), login: user.login, site: user.site }, - { formatSite: (s) => s } + matches( + kbqBuildUsernameText( + { name: this.formatUsername(user), login: user.login, site: user.site }, + { formatSite: (s) => s } + ) ) - .toLowerCase() - .includes(query) ); }); }