('default');
+ /**
+ * Custom projected view for username rendering.
+ * @docs-private
+ */
+ protected readonly customView = contentChild(KbqUsernameCustomView);
+
+ /** @docs-private */
+ protected readonly hasFullName = computed(() => {
+ const userInfo = this.userInfo();
+
+ if (!userInfo) return false;
+
+ return userInfo?.lastName && userInfo?.firstName;
+ });
+
+ /** @docs-private */
+ protected readonly class = computed(() => {
+ return [this.type(), this.mode()].map((modificator) => `${baseClass}_${modificator}`).join(' ');
+ });
+}
diff --git a/packages/docs-examples/components/username/index.ts b/packages/docs-examples/components/username/index.ts
new file mode 100644
index 000000000..424065191
--- /dev/null
+++ b/packages/docs-examples/components/username/index.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { UsernameAsLinkExample } from './username-as-link/username-as-link-example';
+import { UsernameCustomExample } from './username-custom/username-custom-example';
+import { UsernameOverviewExample } from './username-overview/username-overview-example';
+import { UsernamePlaygroundExample } from './username-playground/username-playground-example';
+
+export { UsernameAsLinkExample, UsernameCustomExample, UsernameOverviewExample, UsernamePlaygroundExample };
+
+const EXAMPLES = [
+ UsernameCustomExample,
+ UsernameOverviewExample,
+ UsernamePlaygroundExample,
+ UsernameAsLinkExample
+];
+
+@NgModule({
+ imports: EXAMPLES,
+ exports: EXAMPLES
+})
+export class UsernameExamplesModule {}
diff --git a/packages/docs-examples/components/username/ng-package.json b/packages/docs-examples/components/username/ng-package.json
new file mode 100644
index 000000000..bebf62dcb
--- /dev/null
+++ b/packages/docs-examples/components/username/ng-package.json
@@ -0,0 +1,5 @@
+{
+ "lib": {
+ "entryFile": "index.ts"
+ }
+}
diff --git a/packages/docs-examples/components/username/username-as-link/username-as-link-example.ts b/packages/docs-examples/components/username/username-as-link/username-as-link-example.ts
new file mode 100644
index 000000000..6c948d068
--- /dev/null
+++ b/packages/docs-examples/components/username/username-as-link/username-as-link-example.ts
@@ -0,0 +1,24 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { KbqLinkModule } from '@koobiq/components/link';
+import { KbqUserInfo, KbqUsername } from '@koobiq/components/username';
+
+/**
+ * @title Username as link
+ */
+@Component({
+ selector: 'username-as-link-example',
+ standalone: true,
+ imports: [KbqUsername, KbqLinkModule],
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class UsernameAsLinkExample {
+ userInfo: KbqUserInfo = {
+ firstName: 'Maxwell',
+ middleName: 'Alan',
+ lastName: 'Root',
+ login: 'mroot'
+ };
+}
diff --git a/packages/docs-examples/components/username/username-custom/username-custom-example.ts b/packages/docs-examples/components/username/username-custom/username-custom-example.ts
new file mode 100644
index 000000000..fc4e268f8
--- /dev/null
+++ b/packages/docs-examples/components/username/username-custom/username-custom-example.ts
@@ -0,0 +1,108 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { KbqFormFieldModule } from '@koobiq/components/form-field';
+import { KbqLinkModule } from '@koobiq/components/link';
+import { KbqRadioModule } from '@koobiq/components/radio';
+import { KbqTextareaModule } from '@koobiq/components/textarea';
+import { KbqTitleModule } from '@koobiq/components/title';
+import {
+ KBQ_PROFILE_MAPPING,
+ KbqFormatKeyToProfileMappingExtended,
+ KbqUserInfo,
+ KbqUsernameFormatKey,
+ KbqUsernameMode,
+ KbqUsernameModule,
+ KbqUsernameStyle
+} from '@koobiq/components/username';
+
+const mapping: KbqFormatKeyToProfileMappingExtended = {
+ [KbqUsernameFormatKey.FirstNameShort]: 'firstName',
+ [KbqUsernameFormatKey.FirstNameFull]: 'firstName',
+
+ [KbqUsernameFormatKey.MiddleNameShort]: 'middleName',
+ [KbqUsernameFormatKey.MiddleNameFull]: 'middleName',
+
+ [KbqUsernameFormatKey.LastNameShort]: 'lastName',
+ [KbqUsernameFormatKey.LastNameFull]: 'lastName',
+
+ [KbqUsernameFormatKey.Dot]: undefined
+};
+
+/**
+ * @title Username custom
+ */
+@Component({
+ selector: 'username-custom-example',
+ standalone: true,
+ imports: [
+ FormsModule,
+ KbqUsernameModule,
+ KbqTextareaModule,
+ KbqFormFieldModule,
+ KbqLinkModule,
+ KbqRadioModule,
+ KbqTitleModule
+ ],
+ template: `
+
+
+
+ @let fullName = userInfo | kbqUsernameCustom: fullNameFormat : customMapping;
+ {{ fullName }}
+
+ @if (userInfo?.login) {
+ [{{ userInfo?.login }}]
+ }
+
+
+
+
+
+ Name format
+
+
+
+
+ @for (usernameMode of modes; track usernameMode) {
+
+ {{ usernameMode }}
+
+ }
+
+
+ @for (usernameType of types; track usernameType) {
+
+ {{ usernameType }}
+
+ }
+
+
+ `,
+ styles: `
+ .example-result {
+ padding: var(--kbq-size-m);
+ margin-bottom: var(--kbq-size-m);
+ background: var(--kbq-background-theme-less);
+ border-radius: var(--kbq-size-border-radius);
+ }
+ `,
+ providers: [
+ { provide: KBQ_PROFILE_MAPPING, useValue: mapping }],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class UsernameCustomExample {
+ userInfo: KbqUserInfo = {
+ firstName: 'Maxwell',
+ middleName: 'Alan',
+ lastName: 'Root',
+ login: 'mroot'
+ };
+ selectedMode: KbqUsernameMode = 'inline';
+ selectedType: KbqUsernameStyle = 'default';
+ fullNameFormat = 'F m. l.';
+
+ modes: KbqUsernameMode[] = ['inline', 'stacked', 'text'];
+ types: KbqUsernameStyle[] = ['default', 'error', 'accented', 'inherit'];
+
+ readonly customMapping = mapping;
+}
diff --git a/packages/docs-examples/components/username/username-overview/username-overview-example.ts b/packages/docs-examples/components/username/username-overview/username-overview-example.ts
new file mode 100644
index 000000000..e0b773a9a
--- /dev/null
+++ b/packages/docs-examples/components/username/username-overview/username-overview-example.ts
@@ -0,0 +1,24 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { KbqUserInfo, KbqUsername } from '@koobiq/components/username';
+
+/**
+ * @title Username overview
+ */
+@Component({
+ selector: 'username-overview-example',
+ standalone: true,
+ imports: [KbqUsername],
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class UsernameOverviewExample {
+ userInfo: KbqUserInfo = {
+ firstName: 'Maxwell',
+ middleName: 'Alan',
+ lastName: 'Root',
+ login: 'mroot',
+ site: 'corp'
+ };
+}
diff --git a/packages/docs-examples/components/username/username-playground/username-playground-example.ts b/packages/docs-examples/components/username/username-playground/username-playground-example.ts
new file mode 100644
index 000000000..d6650d382
--- /dev/null
+++ b/packages/docs-examples/components/username/username-playground/username-playground-example.ts
@@ -0,0 +1,80 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { KbqCheckboxModule } from '@koobiq/components/checkbox';
+import { KbqFormFieldModule } from '@koobiq/components/form-field';
+import { KbqRadioModule } from '@koobiq/components/radio';
+import { KbqTextareaModule } from '@koobiq/components/textarea';
+import { KbqUserInfo, KbqUsername, KbqUsernameMode, KbqUsernameStyle } from '@koobiq/components/username';
+
+/**
+ * @title Username playground
+ */
+@Component({
+ selector: 'username-playground-example',
+ standalone: true,
+ imports: [
+ FormsModule,
+ KbqUsername,
+ KbqTextareaModule,
+ KbqFormFieldModule,
+ KbqCheckboxModule,
+ KbqRadioModule
+ ],
+ template: `
+
+
+
+
+
+ Name format
+
+
+
+ isCompact
+
+ @for (usernameMode of modes; track usernameMode) {
+
+ {{ usernameMode }}
+
+ }
+
+
+ @for (usernameType of types; track usernameType) {
+
+ {{ usernameType }}
+
+ }
+
+
+ `,
+ styles: `
+ .example-result {
+ padding: var(--kbq-size-m);
+ margin-bottom: var(--kbq-size-m);
+ background: var(--kbq-background-theme-less);
+ border-radius: var(--kbq-size-border-radius);
+ }
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class UsernamePlaygroundExample {
+ userInfo: KbqUserInfo = {
+ firstName: 'Maxwell',
+ middleName: 'Alan',
+ lastName: 'Root',
+ login: 'mroot'
+ };
+ selectedMode: KbqUsernameMode = 'inline';
+ selectedType: KbqUsernameStyle = 'default';
+ isCompact = false;
+ fullNameFormat = 'f.m.l';
+
+ modes: KbqUsernameMode[] = ['inline', 'stacked', 'text'];
+ types: KbqUsernameStyle[] = ['default', 'error', 'accented', 'inherit'];
+}
diff --git a/packages/docs-examples/example-module.ts b/packages/docs-examples/example-module.ts
index e2d4db2ec..60b2ae173 100644
--- a/packages/docs-examples/example-module.ts
+++ b/packages/docs-examples/example-module.ts
@@ -5411,4 +5411,4 @@ return import('@koobiq/docs-examples/components/validation');
default:
return undefined;
}
-}
\ No newline at end of file
+}
diff --git a/tools/api-extractor/config.json b/tools/api-extractor/config.json
index 6700ceb7c..78ea802d3 100644
--- a/tools/api-extractor/config.json
+++ b/tools/api-extractor/config.json
@@ -52,7 +52,8 @@
"toggle",
"tooltip",
"tree",
- "tree-select"
+ "tree-select",
+ "username"
],
"components-experimental": [
"form-field"
diff --git a/tools/generate-sitemap.ts b/tools/generate-sitemap.ts
index 046fdb0eb..802196e51 100644
--- a/tools/generate-sitemap.ts
+++ b/tools/generate-sitemap.ts
@@ -186,6 +186,9 @@ const paths = [
'components/tree-select/overview',
'components/tree-select/api',
+ 'components/username/overview',
+ 'components/username/api',
+
// ---------------------- Other ----------------------
'other/date-formatter/overview',
'other/date-formatter/api',
diff --git a/tools/public_api_guard/components/username.api.md b/tools/public_api_guard/components/username.api.md
new file mode 100644
index 000000000..91f4e8e00
--- /dev/null
+++ b/tools/public_api_guard/components/username.api.md
@@ -0,0 +1,148 @@
+## API Report File for "koobiq"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+
+import * as i0 from '@angular/core';
+import { InjectionToken } from '@angular/core';
+import { InputSignal } from '@angular/core';
+import { InputSignalWithTransform } from '@angular/core';
+import { PipeTransform } from '@angular/core';
+import { Signal } from '@angular/core';
+
+// @public
+export const KBQ_PROFILE_MAPPING: InjectionToken;
+
+// @public
+export const kbqDefaultFullNameFormat = "lf.m.";
+
+// @public
+export const kbqDefaultFullNameFormatCustom = "L f. m.";
+
+// @public
+export type KbqFormatKeyToProfileMapping = {
+ [key in Exclude]: keyof T | undefined;
+};
+
+// @public
+export type KbqFormatKeyToProfileMappingExtended = {
+ [key in KbqUsernameFormatKey]: keyof T | undefined;
+};
+
+// @public
+export function KbqMappingMissingError(): Error;
+
+// @public
+export type KbqUserInfo = {
+ firstName?: string;
+ lastName?: string;
+ middleName?: string;
+ login?: string;
+ site?: string;
+};
+
+// @public
+export class KbqUsername {
+ protected readonly class: Signal;
+ protected readonly customView: Signal;
+ readonly fullNameFormat: InputSignal;
+ protected readonly hasFullName: Signal;
+ readonly isCompact: InputSignalWithTransform;
+ readonly mode: InputSignal;
+ readonly type: InputSignal;
+ readonly userInfo: InputSignal;
+ // (undocumented)
+ static ɵcmp: i0.ɵɵComponentDeclaration;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+}
+
+// @public
+export class KbqUsernameCustomPipe implements PipeTransform {
+ transform(profile: T, format?: string, customMapping?: KbqFormatKeyToProfileMappingExtended): string;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration, never>;
+ // (undocumented)
+ static ɵpipe: i0.ɵɵPipeDeclaration, "kbqUsernameCustom", true>;
+ // (undocumented)
+ static ɵprov: i0.ɵɵInjectableDeclaration>;
+}
+
+// @public
+export class KbqUsernameCustomView {
+ // (undocumented)
+ static ɵdir: i0.ɵɵDirectiveDeclaration;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+}
+
+// @public
+export enum KbqUsernameFormatKey {
+ // (undocumented)
+ Dot = ".",
+ FirstNameFull = "F",
+ FirstNameShort = "f",
+ LastNameFull = "L",
+ LastNameShort = "l",
+ MiddleNameFull = "M",
+ MiddleNameShort = "m"
+}
+
+// @public
+export type KbqUsernameMode = 'stacked' | 'inline' | 'text';
+
+// @public (undocumented)
+export class KbqUsernameModule {
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+ // (undocumented)
+ static ɵinj: i0.ɵɵInjectorDeclaration;
+ // Warning: (ae-forgotten-export) The symbol "i1" needs to be exported by the entry point index.d.ts
+ // Warning: (ae-forgotten-export) The symbol "i2" needs to be exported by the entry point index.d.ts
+ //
+ // (undocumented)
+ static ɵmod: i0.ɵɵNgModuleDeclaration;
+}
+
+// @public (undocumented)
+export class KbqUsernamePipe implements PipeTransform {
+ transform(profile: T, format?: string, customMapping?: KbqFormatKeyToProfileMapping): string;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration, never>;
+ // (undocumented)
+ static ɵpipe: i0.ɵɵPipeDeclaration, "kbqUsername", true>;
+ // (undocumented)
+ static ɵprov: i0.ɵɵInjectableDeclaration>;
+}
+
+// @public
+export class KbqUsernamePrimary {
+ // (undocumented)
+ static ɵdir: i0.ɵɵDirectiveDeclaration;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+}
+
+// @public
+export class KbqUsernameSecondary {
+ // (undocumented)
+ static ɵdir: i0.ɵɵDirectiveDeclaration;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+}
+
+// @public
+export class KbqUsernameSecondaryHint {
+ // (undocumented)
+ static ɵdir: i0.ɵɵDirectiveDeclaration;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+}
+
+// @public
+export type KbqUsernameStyle = 'default' | 'error' | 'accented' | 'inherit';
+
+// (No @packageDocumentation comment for this package)
+
+```
diff --git a/tsconfig.json b/tsconfig.json
index 16111876b..5d93d9043 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -82,6 +82,7 @@
"@koobiq/components/top-bar": ["packages/components/top-bar/index.ts"],
"@koobiq/components/tree": ["packages/components/tree/index.ts"],
"@koobiq/components/tree-select": ["packages/components/tree-select/index.ts"],
+ "@koobiq/components/username": ["packages/components/username/index.ts"],
"@koobiq/components/vertical-navbar": ["packages/components/vertical-navbar/index.ts"],
"@koobiq/angular-luxon-adapter": ["packages/angular-luxon-adapter/index.ts"],
"@koobiq/angular-moment-adapter": ["packages/angular-moment-adapter/index.ts"],