From 8fd439309aa60c567ca05103d4350e2086e464e2 Mon Sep 17 00:00:00 2001 From: Nikita Guryev Date: Fri, 26 Jun 2026 19:10:47 +0300 Subject: [PATCH 1/6] feat(docs): username filter and search highlight (#DS-5132) --- packages/components-dev/username/module.ts | 1 + packages/components/username/username.en.md | 8 ++ packages/components/username/username.ru.md | 8 ++ .../components/username/index.ts | 12 ++- .../username-search-example.ts | 91 +++++++++++++++++++ 5 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 packages/docs-examples/components/username/username-search/username-search-example.ts diff --git a/packages/components-dev/username/module.ts b/packages/components-dev/username/module.ts index 6b2f797952..e0aea3edf9 100644 --- a/packages/components-dev/username/module.ts +++ b/packages/components-dev/username/module.ts @@ -9,6 +9,7 @@ import { UsernameExamplesModule } from '../../docs-examples/components/username' + `, changeDetection: ChangeDetectionStrategy.OnPush }) diff --git a/packages/components/username/username.en.md b/packages/components/username/username.en.md index b7841f1233..f069ee361c 100644 --- a/packages/components/username/username.en.md +++ b/packages/components/username/username.en.md @@ -21,3 +21,11 @@ To format the full name, use the `kbqUsernameCustom` pipe with a format string a The component can be conveniently used inside links. To visually match the link style, set the `inherit` style — this ensures that color and appearance are inherited from the parent element. + +### Search and highlight + +To filter a list of users by the displayed value, inject `KbqUsernamePipe` as a service and call its `transform` method — it returns the same formatted string that `kbq-username` renders by default. + +To highlight the matched substring, use `kbq-username-custom-view` together with the `kbqHighlightBackground` pipe. + + diff --git a/packages/components/username/username.ru.md b/packages/components/username/username.ru.md index f38196d4ea..4c5b245302 100644 --- a/packages/components/username/username.ru.md +++ b/packages/components/username/username.ru.md @@ -17,3 +17,11 @@ Компонент удобно использовать внутри ссылок. Чтобы он визуально соответствовал стилю ссылки, установите стиль `inherit` — в этом случае цвет и оформление будут унаследованы от родительского элемента. + +### Поиск и подсветка + +Чтобы фильтровать список пользователей по отображаемому значению, используйте `KbqUsernamePipe` как сервис. Метод `transform` возвращает ту же строку, которую по умолчанию формирует `kbq-username`. + +Для подсветки найденной подстроки используйте `kbq-username-custom-view` вместе с пайпом `kbqHighlightBackground`. + + diff --git a/packages/docs-examples/components/username/index.ts b/packages/docs-examples/components/username/index.ts index 4240651915..ec5b05d221 100644 --- a/packages/docs-examples/components/username/index.ts +++ b/packages/docs-examples/components/username/index.ts @@ -3,14 +3,22 @@ import { UsernameAsLinkExample } from './username-as-link/username-as-link-examp import { UsernameCustomExample } from './username-custom/username-custom-example'; import { UsernameOverviewExample } from './username-overview/username-overview-example'; import { UsernamePlaygroundExample } from './username-playground/username-playground-example'; +import { UsernameSearchExample } from './username-search/username-search-example'; -export { UsernameAsLinkExample, UsernameCustomExample, UsernameOverviewExample, UsernamePlaygroundExample }; +export { + UsernameAsLinkExample, + UsernameCustomExample, + UsernameOverviewExample, + UsernamePlaygroundExample, + UsernameSearchExample +}; const EXAMPLES = [ UsernameCustomExample, UsernameOverviewExample, UsernamePlaygroundExample, - UsernameAsLinkExample + UsernameAsLinkExample, + UsernameSearchExample ]; @NgModule({ 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 new file mode 100644 index 0000000000..17868f586d --- /dev/null +++ b/packages/docs-examples/components/username/username-search/username-search-example.ts @@ -0,0 +1,91 @@ +import { ChangeDetectionStrategy, Component, computed, inject } 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 { KbqIconModule } from '@koobiq/components/icon'; +import { KbqInputModule } from '@koobiq/components/input'; +import { KbqUserInfo, KbqUsernameModule, KbqUsernamePipe } from '@koobiq/components/username'; +import { startWith } from 'rxjs'; + +/** + * @title Username search + */ +@Component({ + selector: 'username-search-example', + imports: [ + ReactiveFormsModule, + KbqFormFieldModule, + KbqInputModule, + KbqUsernameModule, + KbqIconModule, + KbqHighlightBackgroundPipe + ], + template: ` + + + + + + +
+ @for (user of filteredUsers(); track user.login) { + + + @let fullName = user | kbqUsername; + + + @if (user.login) { + + } + + + } @empty { + Nothing found + } +
+ `, + styles: ` + .example__users-list { + display: flex; + flex-direction: column; + gap: var(--kbq-size-s); + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + class: 'layout-column layout-gap-m layout-padding-m' + } +}) +export class UsernameSearchExample { + private readonly usernamePipe = inject(KbqUsernamePipe); + + protected readonly searchControl = new FormControl(''); + + private readonly searchText = toSignal(this.searchControl.valueChanges.pipe(startWith('')), { initialValue: '' }); + + protected readonly 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' } + ]; + + protected readonly filteredUsers = computed(() => { + const query = (this.searchText() ?? '').toLowerCase().trim(); + + if (!query) return this.users; + + return this.users.filter((user) => { + const formatted = this.usernamePipe.transform(user).toLowerCase(); + + return formatted.includes(query) || (user.login?.toLowerCase().includes(query) ?? false); + }); + }); +} From 0c06b190e5fb5c82b723c3b4b32c2b7b3791a50d Mon Sep 17 00:00:00 2001 From: Nikita Guryev Date: Mon, 29 Jun 2026 20:14:48 +0300 Subject: [PATCH 2/6] chore: after review --- .../username/username-search/username-search-example.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 17868f586d..ef18120b77 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 @@ -29,19 +29,19 @@ import { startWith } from 'rxjs';
- @for (user of filteredUsers(); track user.login) { + @for (user of filteredUsers(); track user) { @let fullName = user | kbqUsername; @if (user.login) { } @@ -66,7 +66,7 @@ import { startWith } from 'rxjs'; export class UsernameSearchExample { private readonly usernamePipe = inject(KbqUsernamePipe); - protected readonly searchControl = new FormControl(''); + protected readonly searchControl = new FormControl('', { nonNullable: true }); private readonly searchText = toSignal(this.searchControl.valueChanges.pipe(startWith('')), { initialValue: '' }); From eb33964918619bbfc0399ba1f877bb982aa9d15b Mon Sep 17 00:00:00 2001 From: Nikita Guryev Date: Mon, 29 Jun 2026 21:05:43 +0300 Subject: [PATCH 3/6] chore: added username usage in filter bar --- packages/components/username/username.en.md | 8 ++ packages/components/username/username.ru.md | 8 ++ .../components/username/index.ts | 5 +- .../username-filter-bar-option-example.ts | 86 +++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 packages/docs-examples/components/username/username-filter-bar-option/username-filter-bar-option-example.ts diff --git a/packages/components/username/username.en.md b/packages/components/username/username.en.md index f069ee361c..5d20de148f 100644 --- a/packages/components/username/username.en.md +++ b/packages/components/username/username.en.md @@ -29,3 +29,11 @@ To filter a list of users by the displayed value, inject `KbqUsernamePipe` as a To highlight the matched substring, use `kbq-username-custom-view` together with the `kbqHighlightBackground` pipe. + +#### Usage in filter-bar + +The same approach works inside `kbq-pipe-select`. Use `valueTemplate` to render `kbq-username` as an option label. The search text is available via `pipe.searchControl.value` — `pipe` is the `$implicit` template context, which is the pipe component instance itself. + +Set `item.name` to the formatted name (used as the trigger display value) and `item.searchKey` to include login and site so the built-in option filter covers all displayed fields. + + diff --git a/packages/components/username/username.ru.md b/packages/components/username/username.ru.md index 4c5b245302..539649d9fd 100644 --- a/packages/components/username/username.ru.md +++ b/packages/components/username/username.ru.md @@ -25,3 +25,11 @@ Для подсветки найденной подстроки используйте `kbq-username-custom-view` вместе с пайпом `kbqHighlightBackground`. + +#### Использование в filter-bar + +Тот же подход работает внутри `kbq-pipe-select`. Используйте `valueTemplate` для рендеринга `kbq-username` в качестве лейбла опции. Текст поиска доступен через `pipe.searchControl.value` — `pipe` является `$implicit`-контекстом шаблона, то есть экземпляром компонента пайпа. + +Задайте `item.name` форматированным именем (используется как отображаемое значение в триггере), а `item.searchKey` — строкой, включающей логин и сайт, чтобы встроенный фильтр опций охватывал все отображаемые поля. + + diff --git a/packages/docs-examples/components/username/index.ts b/packages/docs-examples/components/username/index.ts index ec5b05d221..5f9e36c7d2 100644 --- a/packages/docs-examples/components/username/index.ts +++ b/packages/docs-examples/components/username/index.ts @@ -1,6 +1,7 @@ import { NgModule } from '@angular/core'; import { UsernameAsLinkExample } from './username-as-link/username-as-link-example'; import { UsernameCustomExample } from './username-custom/username-custom-example'; +import { UsernameFilterBarOptionExample } from './username-filter-bar-option/username-filter-bar-option-example'; import { UsernameOverviewExample } from './username-overview/username-overview-example'; import { UsernamePlaygroundExample } from './username-playground/username-playground-example'; import { UsernameSearchExample } from './username-search/username-search-example'; @@ -8,6 +9,7 @@ import { UsernameSearchExample } from './username-search/username-search-example export { UsernameAsLinkExample, UsernameCustomExample, + UsernameFilterBarOptionExample, UsernameOverviewExample, UsernamePlaygroundExample, UsernameSearchExample @@ -18,7 +20,8 @@ const EXAMPLES = [ UsernameOverviewExample, UsernamePlaygroundExample, UsernameAsLinkExample, - UsernameSearchExample + UsernameSearchExample, + UsernameFilterBarOptionExample ]; @NgModule({ 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 new file mode 100644 index 0000000000..3df7a6be85 --- /dev/null +++ b/packages/docs-examples/components/username/username-filter-bar-option/username-filter-bar-option-example.ts @@ -0,0 +1,86 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, inject, TemplateRef, ViewChild } from '@angular/core'; +import { KbqHighlightPipe } from '@koobiq/components/core'; +import { KbqFilter, KbqFilterBarModule, KbqPipeTemplate, KbqPipeTypes } from '@koobiq/components/filter-bar'; +import { KbqUserInfo, KbqUsernameModule, KbqUsernamePipe } from '@koobiq/components/username'; + +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' } +]; + +/** + * @title Username filter bar option + */ +@Component({ + selector: 'username-filter-bar-option-example', + imports: [KbqFilterBarModule, KbqUsernameModule, KbqHighlightPipe], + template: ` + + @for (pipe of activeFilter.pipes; track pipe) { + + } + + + + @let searchText = pipe.searchControl.value; + + + + @let fullName = option.value | kbqUsername; + + + @if (option.value?.login) { + + } + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class UsernameFilterBarOptionExample implements AfterViewInit { + private readonly usernamePipe = inject(KbqUsernamePipe); + + @ViewChild('userOption') userOptionTemplate: TemplateRef; + + activeFilter: KbqFilter = { + name: '', + readonly: false, + disabled: false, + changed: false, + saved: false, + pipes: [ + { + name: 'Assignee', + type: KbqPipeTypes.Select, + value: null, + search: true, + cleanable: true, + removable: false, + disabled: false + } + ] + }; + + pipeTemplates: KbqPipeTemplate[] = []; + + ngAfterViewInit(): void { + this.pipeTemplates = [ + { + name: 'Assignee', + type: KbqPipeTypes.Select, + values: USERS.map((user) => ({ + name: `${this.usernamePipe.transform(user)} ${[user.login, user.site].filter(Boolean).join(' ')}`, + value: user, + id: user.login + })), + valueTemplate: this.userOptionTemplate, + cleanable: true, + removable: false, + disabled: false + } + ]; + } +} From 1816f25c6603eaa07db0cb72ff904e9147d0fed6 Mon Sep 17 00:00:00 2001 From: Nikita Guryev Date: Tue, 30 Jun 2026 09:23:43 +0300 Subject: [PATCH 4/6] chore: use highlight background pipe --- packages/components-dev/username/module.ts | 1 + .../username-filter-bar-option-example.ts | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/components-dev/username/module.ts b/packages/components-dev/username/module.ts index e0aea3edf9..2bdc6ab94f 100644 --- a/packages/components-dev/username/module.ts +++ b/packages/components-dev/username/module.ts @@ -10,6 +10,7 @@ import { UsernameExamplesModule } from '../../docs-examples/components/username' + `, changeDetection: ChangeDetectionStrategy.OnPush }) 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 3df7a6be85..a1d6d1a14f 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 @@ -1,5 +1,5 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, inject, TemplateRef, ViewChild } from '@angular/core'; -import { KbqHighlightPipe } from '@koobiq/components/core'; +import { KbqHighlightBackgroundPipe } from '@koobiq/components/core'; import { KbqFilter, KbqFilterBarModule, KbqPipeTemplate, KbqPipeTypes } from '@koobiq/components/filter-bar'; import { KbqUserInfo, KbqUsernameModule, KbqUsernamePipe } from '@koobiq/components/username'; @@ -15,7 +15,7 @@ const USERS: KbqUserInfo[] = [ */ @Component({ selector: 'username-filter-bar-option-example', - imports: [KbqFilterBarModule, KbqUsernameModule, KbqHighlightPipe], + imports: [KbqFilterBarModule, KbqUsernameModule, KbqHighlightBackgroundPipe], template: ` @for (pipe of activeFilter.pipes; track pipe) { @@ -29,10 +29,13 @@ const USERS: KbqUserInfo[] = [ @let fullName = option.value | kbqUsername; - + @if (option.value?.login) { - + } From 1e1162f10bad45c23e13eb2a88e9639c39493cce Mon Sep 17 00:00:00 2001 From: Nikita Guryev Date: Tue, 30 Jun 2026 18:10:57 +0300 Subject: [PATCH 5/6] chore: added username search text function build --- .../__snapshots__/username.spec.ts.snap | 14 ++++++++ packages/components/username/username.pipe.ts | 29 ++++++++++++++++ packages/components/username/username.spec.ts | 34 ++++++++++++++++++- .../username-filter-bar-option-example.ts | 14 ++++---- .../username-search-example.ts | 15 ++++---- .../components/username.api.md | 11 ++++++ 6 files changed, 104 insertions(+), 13 deletions(-) diff --git a/packages/components/username/__snapshots__/username.spec.ts.snap b/packages/components/username/__snapshots__/username.spec.ts.snap index 1146e8d025..20a6169e87 100644 --- a/packages/components/username/__snapshots__/username.spec.ts.snap +++ b/packages/components/username/__snapshots__/username.spec.ts.snap @@ -18,3 +18,17 @@ exports[`KbqUsernamePipe should format full name using custom format 1`] = ` "Alice B. C.", ] `; + +exports[`kbqBuildUsernameText should append login after name 1`] = `"Root M. A. mroot"`; + +exports[`kbqBuildUsernameText should include site without login 1`] = `"Root M. A. (corp)"`; + +exports[`kbqBuildUsernameText should return name only when login and site are absent 1`] = `"Root M. A."`; + +exports[`kbqBuildUsernameText should skip empty name and join remaining parts 1`] = `"mroot (corp)"`; + +exports[`kbqBuildUsernameText should use custom formatLogin 1`] = `"Root M. A. [@mroot]"`; + +exports[`kbqBuildUsernameText should use custom formatSite 1`] = `"Root M. A. corp"`; + +exports[`kbqBuildUsernameText should wrap site in parentheses by default 1`] = `"Root M. A. mroot (corp)"`; diff --git a/packages/components/username/username.pipe.ts b/packages/components/username/username.pipe.ts index 88b89642ec..9a98030064 100644 --- a/packages/components/username/username.pipe.ts +++ b/packages/components/username/username.pipe.ts @@ -6,6 +6,7 @@ import { KbqMappingMissingError } from './constants'; import { KbqFormatKeyToProfileMapping, KbqFormatKeyToProfileMappingExtended, KbqUsernameFormatKey } from './types'; +import { KbqUserInfo } from './username'; @Injectable({ providedIn: 'root' }) @Pipe({ @@ -95,3 +96,31 @@ export class KbqUsernameCustomPipe implements PipeTransform { return result.trim(); } } + +export interface KbqUsernameTextOptions { + /** Formats the login segment. Defaults to identity. */ + formatLogin?: (login: string) => string; + /** + * Formats the site segment. + * Defaults to wrapping in parentheses, matching kbq-username display. + */ + formatSite?: (site: string) => string; +} + +/** + * Builds a full username string from a pre-formatted name plus optional login and site, + * mirroring the text rendered by `kbq-username`. + * + * Provide custom `formatLogin` / `formatSite` to tailor the output. + */ +export function kbqBuildUsernameText( + data: { name: string } & Partial>, + options?: KbqUsernameTextOptions +): string { + const formatLogin = options?.formatLogin ?? ((login) => login); + const formatSite = options?.formatSite ?? ((site) => `(${site})`); + + return [data.name, data.login && formatLogin(data.login), data.site && formatSite(data.site)] + .filter(Boolean) + .join(' '); +} diff --git a/packages/components/username/username.spec.ts b/packages/components/username/username.spec.ts index 19ecd024fe..0b5923ea88 100644 --- a/packages/components/username/username.spec.ts +++ b/packages/components/username/username.spec.ts @@ -9,7 +9,7 @@ import { KbqUsernameStyle } from './types'; import { KbqUsername, KbqUsernameCustomView } from './username'; -import { KbqUsernameCustomPipe, KbqUsernamePipe } from './username.pipe'; +import { kbqBuildUsernameText, KbqUsernameCustomPipe, KbqUsernamePipe } from './username.pipe'; const createComponent = (component: Type, providers: any[] = []): ComponentFixture => { TestBed.configureTestingModule({ imports: [component], providers }).compileComponents(); @@ -120,6 +120,38 @@ describe(KbqUsernamePipe.name, () => { }); }); +describe('kbqBuildUsernameText', () => { + it('should return name only when login and site are absent', () => { + expect(kbqBuildUsernameText({ name: 'Root M. A.' })).toMatchSnapshot(); + }); + + it('should append login after name', () => { + expect(kbqBuildUsernameText({ name: 'Root M. A.', login: 'mroot' })).toMatchSnapshot(); + }); + + it('should wrap site in parentheses by default', () => { + expect(kbqBuildUsernameText({ name: 'Root M. A.', login: 'mroot', site: 'corp' })).toMatchSnapshot(); + }); + + it('should include site without login', () => { + expect(kbqBuildUsernameText({ name: 'Root M. A.', site: 'corp' })).toMatchSnapshot(); + }); + + it('should use custom formatSite', () => { + expect(kbqBuildUsernameText({ name: 'Root M. A.', site: 'corp' }, { formatSite: (s) => s })).toMatchSnapshot(); + }); + + it('should use custom formatLogin', () => { + expect( + kbqBuildUsernameText({ name: 'Root M. A.', login: 'mroot' }, { formatLogin: (s) => `[@${s}]` }) + ).toMatchSnapshot(); + }); + + it('should skip empty name and join remaining parts', () => { + expect(kbqBuildUsernameText({ name: '', login: 'mroot', site: 'corp' })).toMatchSnapshot(); + }); +}); + describe(KbqUsername.name, () => { it('should use default input values', () => { const { debugElement } = createComponent(TestComponent); 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 a1d6d1a14f..3a067ee43e 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 @@ -1,7 +1,7 @@ -import { AfterViewInit, ChangeDetectionStrategy, Component, inject, TemplateRef, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, inject, TemplateRef, viewChild } from '@angular/core'; import { KbqHighlightBackgroundPipe } from '@koobiq/components/core'; import { KbqFilter, KbqFilterBarModule, KbqPipeTemplate, KbqPipeTypes } from '@koobiq/components/filter-bar'; -import { KbqUserInfo, KbqUsernameModule, KbqUsernamePipe } from '@koobiq/components/username'; +import { kbqBuildUsernameText, KbqUserInfo, KbqUsernameModule, KbqUsernamePipe } from '@koobiq/components/username'; const USERS: KbqUserInfo[] = [ { firstName: 'Maxwell', middleName: 'Alan', lastName: 'Root', login: 'mroot', site: 'corp' }, @@ -45,8 +45,7 @@ const USERS: KbqUserInfo[] = [ }) export class UsernameFilterBarOptionExample implements AfterViewInit { private readonly usernamePipe = inject(KbqUsernamePipe); - - @ViewChild('userOption') userOptionTemplate: TemplateRef; + private readonly userOptionTemplate = viewChild>('userOption'); activeFilter: KbqFilter = { name: '', @@ -75,11 +74,14 @@ export class UsernameFilterBarOptionExample implements AfterViewInit { name: 'Assignee', type: KbqPipeTypes.Select, values: USERS.map((user) => ({ - name: `${this.usernamePipe.transform(user)} ${[user.login, user.site].filter(Boolean).join(' ')}`, + name: kbqBuildUsernameText( + { name: this.usernamePipe.transform(user), login: user.login, site: user.site }, + { formatSite: (s) => s } + ), value: user, id: user.login })), - valueTemplate: this.userOptionTemplate, + valueTemplate: this.userOptionTemplate(), cleanable: true, removable: false, disabled: false 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 ef18120b77..d30b58da95 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 @@ -5,7 +5,7 @@ import { KbqHighlightBackgroundPipe } from '@koobiq/components/core'; import { KbqFormFieldModule } from '@koobiq/components/form-field'; import { KbqIconModule } from '@koobiq/components/icon'; import { KbqInputModule } from '@koobiq/components/input'; -import { KbqUserInfo, KbqUsernameModule, KbqUsernamePipe } from '@koobiq/components/username'; +import { kbqBuildUsernameText, KbqUserInfo, KbqUsernameModule, KbqUsernamePipe } from '@koobiq/components/username'; import { startWith } from 'rxjs'; /** @@ -82,10 +82,13 @@ export class UsernameSearchExample { if (!query) return this.users; - return this.users.filter((user) => { - const formatted = this.usernamePipe.transform(user).toLowerCase(); - - return formatted.includes(query) || (user.login?.toLowerCase().includes(query) ?? false); - }); + return this.users.filter((user) => + kbqBuildUsernameText( + { name: this.usernamePipe.transform(user), login: user.login, site: user.site }, + { formatSite: (s) => s } + ) + .toLowerCase() + .includes(query) + ); }); } diff --git a/tools/public_api_guard/components/username.api.md b/tools/public_api_guard/components/username.api.md index 3b4f48174e..a01e755026 100644 --- a/tools/public_api_guard/components/username.api.md +++ b/tools/public_api_guard/components/username.api.md @@ -11,6 +11,11 @@ import { PipeTransform } from '@angular/core'; // @public export const KBQ_PROFILE_MAPPING: InjectionToken; +// @public +export function kbqBuildUsernameText(data: { + name: string; +} & Partial>, options?: KbqUsernameTextOptions): string; + // @public export const kbqDefaultFullNameFormat = "lf.m."; @@ -137,6 +142,12 @@ export class KbqUsernameSecondaryHint { // @public export type KbqUsernameStyle = 'default' | 'error' | 'accented' | 'inherit'; +// @public (undocumented) +export interface KbqUsernameTextOptions { + formatLogin?: (login: string) => string; + formatSite?: (site: string) => string; +} + // (No @packageDocumentation comment for this package) ``` From fe1f91ff8d98e23068d5bf4fee4dfd893a9b98c4 Mon Sep 17 00:00:00 2001 From: Roman Turov <31649219+rmnturov@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:34:17 +0700 Subject: [PATCH 6/6] docs(username): refine search and highlight section wording (#DS-5132) --- packages/components/username/username.en.md | 10 +++++----- packages/components/username/username.ru.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/components/username/username.en.md b/packages/components/username/username.en.md index 5d20de148f..36a408e38a 100644 --- a/packages/components/username/username.en.md +++ b/packages/components/username/username.en.md @@ -24,16 +24,16 @@ The component can be conveniently used inside links. To visually match the link ### Search and highlight -To filter a list of users by the displayed value, inject `KbqUsernamePipe` as a service and call its `transform` method — it returns the same formatted string that `kbq-username` renders by default. +To filter a list of users by the displayed name, inject `KbqUsernamePipe` as a service and call its `transform` method — it returns the same string the component renders by default. -To highlight the matched substring, use `kbq-username-custom-view` together with the `kbqHighlightBackground` pipe. +The matched fragment is easy to highlight in a custom template with the `kbqHighlightBackground` pipe. -#### Usage in filter-bar +#### Search in Filter bar -The same approach works inside `kbq-pipe-select`. Use `valueTemplate` to render `kbq-username` as an option label. The search text is available via `pipe.searchControl.value` — `pipe` is the `$implicit` template context, which is the pipe component instance itself. +The same approach works in the filter bar, inside `kbq-pipe-select`. The look of an option is defined by `valueTemplate`, so you can render the username component as its label. The entered search text comes from the template context: the `$implicit` variable holds the pipe component itself, and its `searchControl.value` property keeps the current string. -Set `item.name` to the formatted name (used as the trigger display value) and `item.searchKey` to include login and site so the built-in option filter covers all displayed fields. +Put the formatted name in the `name` property — it is used as the trigger display value. Add the login and site to `searchKey` so the built-in filter covers every visible field. diff --git a/packages/components/username/username.ru.md b/packages/components/username/username.ru.md index 539649d9fd..4aa137c73a 100644 --- a/packages/components/username/username.ru.md +++ b/packages/components/username/username.ru.md @@ -20,16 +20,16 @@ ### Поиск и подсветка -Чтобы фильтровать список пользователей по отображаемому значению, используйте `KbqUsernamePipe` как сервис. Метод `transform` возвращает ту же строку, которую по умолчанию формирует `kbq-username`. +Чтобы фильтровать список пользователей по отображаемому имени, внедрите `KbqUsernamePipe` как сервис и вызовите его метод `transform` — он вернет ту же строку, которую компонент отображает по умолчанию. -Для подсветки найденной подстроки используйте `kbq-username-custom-view` вместе с пайпом `kbqHighlightBackground`. +Найденный фрагмент подсветите в пользовательском шаблоне с помощью пайпа `kbqHighlightBackground`. -#### Использование в filter-bar +#### Поиск в Filter bar -Тот же подход работает внутри `kbq-pipe-select`. Используйте `valueTemplate` для рендеринга `kbq-username` в качестве лейбла опции. Текст поиска доступен через `pipe.searchControl.value` — `pipe` является `$implicit`-контекстом шаблона, то есть экземпляром компонента пайпа. +Тот же подход работает в панели фильтров, внутри `kbq-pipe-select`. Внешний вид опции определяет `valueTemplate` — в нем можно вывести компонент имени пользователя как подпись опции. Текст поиска доступен из контекста шаблона: переменная `$implicit` содержит компонент пайпа, а его свойство `searchControl.value` хранит текущую строку. -Задайте `item.name` форматированным именем (используется как отображаемое значение в триггере), а `item.searchKey` — строкой, включающей логин и сайт, чтобы встроенный фильтр опций охватывал все отображаемые поля. +Отображаемое имя укажите в свойстве `name` — оно используется как значение в триггере. В `searchKey` добавьте логин и сайт, чтобы встроенный фильтр охватывал все видимые поля.