From 801655c81e886a878545b7cfa67f5447514b49db Mon Sep 17 00:00:00 2001 From: Nikita Guryev Date: Tue, 27 Oct 2026 19:13:43 +0300 Subject: [PATCH] fix(username): show the site, highlight multi-word queries and add compact and no-last-name examples (#DS-5717) --- packages/components-dev/username/module.ts | 1 + packages/components/username/username.en.mdx | 2 + packages/components/username/username.ru.mdx | 2 + .../components/username/index.ts | 3 ++ .../username-compact-example.ts | 26 ++++++++++++ .../username-filter-bar-option-example.ts | 25 ++++++++---- .../username-search-example.ts | 40 +++++++++++-------- packages/docs-examples/example-module.ts | 13 ++++++ packages/docs-examples/loader/index.ts | 2 + 9 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 packages/docs-examples/components/username/username-compact/username-compact-example.ts diff --git a/packages/components-dev/username/module.ts b/packages/components-dev/username/module.ts index 2bdc6ab94f..28ae4f269a 100644 --- a/packages/components-dev/username/module.ts +++ b/packages/components-dev/username/module.ts @@ -8,6 +8,7 @@ import { UsernameExamplesModule } from '../../docs-examples/components/username' + diff --git a/packages/components/username/username.en.mdx b/packages/components/username/username.en.mdx index 3caa29567d..b0fce107ab 100644 --- a/packages/components/username/username.en.mdx +++ b/packages/components/username/username.en.mdx @@ -14,6 +14,8 @@ Displaying the username means showing a set of attributes from the internal syst `isCompact` renders everything as a single line: the name if the profile has one, the login otherwise — never both. Use it where the row has no space for two parts, such as a select trigger. + + ### Custom template If flexible layout is required and the default template doesn’t meet your needs, project a ``; it composes with any `mode` and `type`, so consistency with the design system is maintained. diff --git a/packages/components/username/username.ru.mdx b/packages/components/username/username.ru.mdx index 2f80c5cce6..a862a7ad26 100644 --- a/packages/components/username/username.ru.mdx +++ b/packages/components/username/username.ru.mdx @@ -12,6 +12,8 @@ `isCompact` выводит всё одной строкой: имя, если оно есть в профиле, иначе логин, но не оба сразу. Используйте этот режим там, где на две части нет места, например в триггере селекта. + + ### Пользовательский шаблон Если стандартного шаблона недостаточно, спроецируйте ``: он сочетается с любыми `mode` и `type` и сохраняет согласованность с дизайн-системой. diff --git a/packages/docs-examples/components/username/index.ts b/packages/docs-examples/components/username/index.ts index 5f9e36c7d2..3e7d34eb37 100644 --- a/packages/docs-examples/components/username/index.ts +++ b/packages/docs-examples/components/username/index.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { UsernameAsLinkExample } from './username-as-link/username-as-link-example'; +import { UsernameCompactExample } from './username-compact/username-compact-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'; @@ -8,6 +9,7 @@ import { UsernameSearchExample } from './username-search/username-search-example export { UsernameAsLinkExample, + UsernameCompactExample, UsernameCustomExample, UsernameFilterBarOptionExample, UsernameOverviewExample, @@ -16,6 +18,7 @@ export { }; const EXAMPLES = [ + UsernameCompactExample, UsernameCustomExample, UsernameOverviewExample, UsernamePlaygroundExample, diff --git a/packages/docs-examples/components/username/username-compact/username-compact-example.ts b/packages/docs-examples/components/username/username-compact/username-compact-example.ts new file mode 100644 index 0000000000..0410c17d3f --- /dev/null +++ b/packages/docs-examples/components/username/username-compact/username-compact-example.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { KbqUserInfo, KbqUsername } from '@koobiq/components/username'; + +/** + * @title Username compact + */ +@Component({ + selector: 'username-compact-example', + imports: [KbqUsername], + template: ` + @for (user of users; track user) { + + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + class: 'layout-column layout-gap-s layout-padding-m' + } +}) +export class UsernameCompactExample { + protected readonly users: KbqUserInfo[] = [ + { firstName: 'Maxwell', middleName: 'Alan', lastName: 'Root', login: 'mroot', site: 'corp' }, + { firstName: 'Madonna', login: 'madonna' }, + { login: 'efox', site: 'dev' } + ]; +} 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..f53f58d4bf 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,18 +30,23 @@ 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) { + + } + } @@ -73,6 +79,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..8aa78053b2 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 @@ -38,16 +38,18 @@ import { startWith } from 'rxjs'; @let fullName = user | kbqUsername; - + @if (user.login) { - + + + @if (user.site) { + + } + } @@ -79,21 +81,25 @@ 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' } ]; + // 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(() => (this.searchText() ?? '').split(/\s+/).filter(Boolean)); + protected readonly filteredUsers = computed(() => { - const query = (this.searchText() ?? '').toLowerCase().trim(); + const keywords = this.keywords().map((word) => word.toLowerCase()); - if (!query) return this.users; + if (!keywords.length) return this.users; - return this.users.filter((user) => - kbqBuildUsernameText( + return this.users.filter((user) => { + const text = kbqBuildUsernameText( { name: this.formatUsername(user), login: user.login, site: user.site }, { formatSite: (s) => s } - ) - .toLowerCase() - .includes(query) - ); + ).toLowerCase(); + + return keywords.every((word) => text.includes(word)); + }); }); } diff --git a/packages/docs-examples/example-module.ts b/packages/docs-examples/example-module.ts index 8d7d7b04f7..711d9fbc2c 100644 --- a/packages/docs-examples/example-module.ts +++ b/packages/docs-examples/example-module.ts @@ -7621,6 +7621,18 @@ export const EXAMPLE_COMPONENTS: {[id: string]: LiveExample} = { "primaryFile": "username-as-link-example.ts", "importPath": "components/username" }, + "username-compact": { + "packagePath": "components/username/username-compact", + "title": "Username compact", + "componentName": "UsernameCompactExample", + "files": [ + "username-compact-example.ts" + ], + "localImportFiles": [], + "selector": "username-compact-example", + "primaryFile": "username-compact-example.ts", + "importPath": "components/username" + }, "username-custom": { "packagePath": "components/username/username-custom", "title": "Username custom", @@ -8518,6 +8530,7 @@ export type LiveExampleId = | 'tree-state-saving' | 'tree-toggle-on-click' | 'username-as-link' + | 'username-compact' | 'username-custom' | 'username-filter-bar-option' | 'username-overview' diff --git a/packages/docs-examples/loader/index.ts b/packages/docs-examples/loader/index.ts index 3738f7c2f9..dc59d1b329 100644 --- a/packages/docs-examples/loader/index.ts +++ b/packages/docs-examples/loader/index.ts @@ -1241,6 +1241,8 @@ export async function loadExampleComponent(id: string): Promise | return import('@koobiq/docs-examples/components/tree').then((m) => m.TreeToggleOnClickExample); case 'username-as-link': return import('@koobiq/docs-examples/components/username').then((m) => m.UsernameAsLinkExample); + case 'username-compact': + return import('@koobiq/docs-examples/components/username').then((m) => m.UsernameCompactExample); case 'username-custom': return import('@koobiq/docs-examples/components/username').then((m) => m.UsernameCustomExample); case 'username-filter-bar-option':