Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components-dev/username/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { UsernameExamplesModule } from '../../docs-examples/components/username'
<username-playground-example />
<username-custom-example />
<username-as-link-example />
<username-search-example />
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
Expand Down
16 changes: 16 additions & 0 deletions packages/components/username/username.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,19 @@ 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.

<!-- example(username-as-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 highlight the matched substring, use `kbq-username-custom-view` together with the `kbqHighlightBackground` pipe.

<!-- example(username-search) -->

#### 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.

<!-- example(username-filter-bar-option) -->
16 changes: 16 additions & 0 deletions packages/components/username/username.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,19 @@
Компонент удобно использовать внутри ссылок. Чтобы он визуально соответствовал стилю ссылки, установите стиль `inherit` — в этом случае цвет и оформление будут унаследованы от родительского элемента.

<!-- example(username-as-link) -->

### Поиск и подсветка

Чтобы фильтровать список пользователей по отображаемому значению, используйте `KbqUsernamePipe` как сервис. Метод `transform` возвращает ту же строку, которую по умолчанию формирует `kbq-username`.

Для подсветки найденной подстроки используйте `kbq-username-custom-view` вместе с пайпом `kbqHighlightBackground`.

<!-- example(username-search) -->

#### Использование в filter-bar

Тот же подход работает внутри `kbq-pipe-select`. Используйте `valueTemplate` для рендеринга `kbq-username` в качестве лейбла опции. Текст поиска доступен через `pipe.searchControl.value` — `pipe` является `$implicit`-контекстом шаблона, то есть экземпляром компонента пайпа.

Задайте `item.name` форматированным именем (используется как отображаемое значение в триггере), а `item.searchKey` — строкой, включающей логин и сайт, чтобы встроенный фильтр опций охватывал все отображаемые поля.

<!-- example(username-filter-bar-option) -->
15 changes: 13 additions & 2 deletions packages/docs-examples/components/username/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
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';

export { UsernameAsLinkExample, UsernameCustomExample, UsernameOverviewExample, UsernamePlaygroundExample };
export {
UsernameAsLinkExample,
UsernameCustomExample,
UsernameFilterBarOptionExample,
UsernameOverviewExample,
UsernamePlaygroundExample,
UsernameSearchExample
};

const EXAMPLES = [
UsernameCustomExample,
UsernameOverviewExample,
UsernamePlaygroundExample,
UsernameAsLinkExample
UsernameAsLinkExample,
UsernameSearchExample,
UsernameFilterBarOptionExample
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -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: `
<kbq-filter-bar [pipeTemplates]="pipeTemplates" [(filter)]="activeFilter">
@for (pipe of activeFilter.pipes; track pipe) {
<ng-container *kbqPipe="pipe" />
}
</kbq-filter-bar>

<ng-template #userOption let-pipe let-option="option">
@let searchText = pipe.searchControl.value;

<kbq-username>
<kbq-username-custom-view>
@let fullName = option.value | kbqUsername;
<span kbqUsernamePrimary [innerHTML]="fullName | mcHighlight: searchText"></span>

@if (option.value?.login) {
<span kbqUsernameSecondary [innerHTML]="option.value.login | mcHighlight: searchText"></span>
}
</kbq-username-custom-view>
</kbq-username>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UsernameFilterBarOptionExample implements AfterViewInit {
private readonly usernamePipe = inject(KbqUsernamePipe);

@ViewChild('userOption') userOptionTemplate: TemplateRef<any>;

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
}
];
}
}
Original file line number Diff line number Diff line change
@@ -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: `
<kbq-form-field>
<i kbqPrefix kbq-icon="kbq-magnifying-glass_16"></i>
<input kbqInput type="text" placeholder="Search" autocomplete="off" [formControl]="searchControl" />
<kbq-cleaner />
</kbq-form-field>

<div class="example__users-list">
@for (user of filteredUsers(); track user) {
<kbq-username>
<kbq-username-custom-view>
@let fullName = user | kbqUsername;
<span
kbqUsernamePrimary
[innerHTML]="fullName | kbqHighlightBackground: searchControl.value.trim()"
></span>

@if (user.login) {
<span
kbqUsernameSecondary
[innerHTML]="user.login | kbqHighlightBackground: searchControl.value.trim()"
></span>
}
</kbq-username-custom-view>
</kbq-username>
} @empty {
<span class="kbq-text-normal kbq-second">Nothing found</span>
}
</div>
`,
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('', { nonNullable: true });

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);
});
});
}
Loading