From 6d95f80a9a7e0db417a3b2d326900d09bf7e032f Mon Sep 17 00:00:00 2001 From: lskramarov Date: Thu, 4 Sep 2025 17:54:34 +0300 Subject: [PATCH 01/17] feat(app-switcher): added new component (#DS-4040) --- angular.json | 30 ++ package.json | 1 + packages/components-dev/app-switcher/main.ts | 9 + .../components-dev/app-switcher/module.ts | 36 +++ .../components-dev/app-switcher/styles.scss | 0 .../components-dev/app-switcher/template.html | 1 + .../app-switcher/tsconfig.app.json | 5 + .../app-switcher/_app-switcher-theme.scss | 13 + .../app-switcher/app-switcher-animations.ts | 27 ++ .../app-switcher/app-switcher.component.html | 11 + .../app-switcher/app-switcher.component.ts | 260 ++++++++++++++++++ .../app-switcher/app-switcher.en.md | 5 + .../app-switcher/app-switcher.module.ts | 43 +++ .../app-switcher/app-switcher.ru.md | 0 .../components/app-switcher/app-switcher.scss | 8 + .../app-switcher/app-switcher.spec.ts | 66 +++++ .../app-switcher/examples.app-switcher.en.md | 1 + .../app-switcher/examples.app-switcher.ru.md | 0 packages/components/app-switcher/index.ts | 1 + .../components/app-switcher/ng-package.json | 5 + .../components/app-switcher/public-api.ts | 3 + .../components/popover/popover.component.ts | 2 +- tsconfig.json | 1 + 23 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 packages/components-dev/app-switcher/main.ts create mode 100644 packages/components-dev/app-switcher/module.ts create mode 100644 packages/components-dev/app-switcher/styles.scss create mode 100644 packages/components-dev/app-switcher/template.html create mode 100644 packages/components-dev/app-switcher/tsconfig.app.json create mode 100644 packages/components/app-switcher/_app-switcher-theme.scss create mode 100644 packages/components/app-switcher/app-switcher-animations.ts create mode 100644 packages/components/app-switcher/app-switcher.component.html create mode 100644 packages/components/app-switcher/app-switcher.component.ts create mode 100644 packages/components/app-switcher/app-switcher.en.md create mode 100644 packages/components/app-switcher/app-switcher.module.ts create mode 100644 packages/components/app-switcher/app-switcher.ru.md create mode 100644 packages/components/app-switcher/app-switcher.scss create mode 100644 packages/components/app-switcher/app-switcher.spec.ts create mode 100644 packages/components/app-switcher/examples.app-switcher.en.md create mode 100644 packages/components/app-switcher/examples.app-switcher.ru.md create mode 100644 packages/components/app-switcher/index.ts create mode 100644 packages/components/app-switcher/ng-package.json create mode 100644 packages/components/app-switcher/public-api.ts diff --git a/angular.json b/angular.json index 3b8baadec..f3850d12d 100644 --- a/angular.json +++ b/angular.json @@ -500,6 +500,36 @@ } } }, + "dev-app-switcher": { + "projectType": "application", + "root": "packages/components-dev/app-switcher", + "sourceRoot": "packages/components-dev/app-switcher", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": { + "base": "dist/components-dev/app-switcher" + }, + "tsConfig": "packages/components-dev/app-switcher/tsconfig.app.json", + "index": "packages/components-dev/index.html", + "styles": ["packages/components-dev/main.scss"], + "polyfills": ["zone.js"], + "extractLicenses": false, + "sourceMap": true, + "optimization": false, + "namedChunks": true, + "browser": "packages/components-dev/app-switcher/main.ts" + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "dev-app-switcher:build" + } + } + } + }, "dev-autocomplete": { "projectType": "application", "root": "packages/components-dev/autocomplete", diff --git a/package.json b/package.json index a22b9013c..0dc0d909c 100644 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ "dev:actions-panel": "ng serve dev-actions-panel --port 3003", "dev:ag-grid": "ng serve dev-ag-grid --port 3003", "dev:alert": "ng serve dev-alert --port 3003", + "dev:app-switcher": "ng serve dev-app-switcher --port 3003", "dev:autocomplete": "ng serve dev-autocomplete --port 3003", "dev:badge": "ng serve dev-badge --port 3003", "dev:breadcrumbs": "ng serve dev-breadcrumbs --port 3003", diff --git a/packages/components-dev/app-switcher/main.ts b/packages/components-dev/app-switcher/main.ts new file mode 100644 index 000000000..2bbc80baf --- /dev/null +++ b/packages/components-dev/app-switcher/main.ts @@ -0,0 +1,9 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { DevApp } from './module'; + +bootstrapApplication(DevApp, { + providers: [ + provideAnimations() + ] +}).catch((error) => console.error(error)); diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts new file mode 100644 index 000000000..3548e885b --- /dev/null +++ b/packages/components-dev/app-switcher/module.ts @@ -0,0 +1,36 @@ +import { A11yModule } from '@angular/cdk/a11y'; +import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; +import { KbqButtonModule } from '@koobiq/components/button'; +import { KbqIcon } from '@koobiq/components/icon'; +import { PopoverExamplesModule } from 'packages/docs-examples/components/popover'; + +@Component({ + standalone: true, + imports: [PopoverExamplesModule, KbqButtonModule, KbqIcon], + selector: 'dev-examples', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush +}) +class DevExamples {} + +@Component({ + standalone: true, + selector: 'dev-app', + styleUrls: ['./styles.scss'], + templateUrl: './template.html', + imports: [ + A11yModule, + FormsModule, + KbqAppSwitcherModule, + DevExamples + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DevApp {} diff --git a/packages/components-dev/app-switcher/styles.scss b/packages/components-dev/app-switcher/styles.scss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/components-dev/app-switcher/template.html b/packages/components-dev/app-switcher/template.html new file mode 100644 index 000000000..c6a5b7b43 --- /dev/null +++ b/packages/components-dev/app-switcher/template.html @@ -0,0 +1 @@ + diff --git a/packages/components-dev/app-switcher/tsconfig.app.json b/packages/components-dev/app-switcher/tsconfig.app.json new file mode 100644 index 000000000..81fb90e7c --- /dev/null +++ b/packages/components-dev/app-switcher/tsconfig.app.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.json", + "include": ["**/*.d.ts"], + "files": ["main.ts"] +} diff --git a/packages/components/app-switcher/_app-switcher-theme.scss b/packages/components/app-switcher/_app-switcher-theme.scss new file mode 100644 index 000000000..7975628e2 --- /dev/null +++ b/packages/components/app-switcher/_app-switcher-theme.scss @@ -0,0 +1,13 @@ +@use '../core/styles/common/tokens' as *; + +@mixin kbq-app-switcher-theme() { + .kbq-app-switcher { + + } +} + +@mixin kbq-app-switcher-typography() { + .kbq-app-switcher { + @include kbq-typography-level-to-styles-css-variables(typography, text-normal); + } +} diff --git a/packages/components/app-switcher/app-switcher-animations.ts b/packages/components/app-switcher/app-switcher-animations.ts new file mode 100644 index 000000000..ce382f774 --- /dev/null +++ b/packages/components/app-switcher/app-switcher-animations.ts @@ -0,0 +1,27 @@ +import { animate, AnimationTriggerMetadata, state, style, transition, trigger } from '@angular/animations'; + +export const kbqAppSwitcherAnimations: { + readonly state: AnimationTriggerMetadata; +} = { + /** Animation that transitions a tooltip in and out. */ + state: trigger('state', [ + state( + 'initial', + style({ + opacity: 0, + transform: 'scale(1, 0.8)' + }) + ), + transition( + '* => visible', + animate( + '120ms cubic-bezier(0, 0, 0.2, 1)', + style({ + opacity: 1, + transform: 'scale(1, 1)' + }) + ) + ), + transition('* => hidden', animate('100ms linear', style({ opacity: 0 }))) + ]) +}; diff --git a/packages/components/app-switcher/app-switcher.component.html b/packages/components/app-switcher/app-switcher.component.html new file mode 100644 index 000000000..b5867a35f --- /dev/null +++ b/packages/components/app-switcher/app-switcher.component.html @@ -0,0 +1,11 @@ +
+
kbq-app-switcher__container
+
diff --git a/packages/components/app-switcher/app-switcher.component.ts b/packages/components/app-switcher/app-switcher.component.ts new file mode 100644 index 000000000..6e57ba4b4 --- /dev/null +++ b/packages/components/app-switcher/app-switcher.component.ts @@ -0,0 +1,260 @@ +import { CdkTrapFocus } from '@angular/cdk/a11y'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + CdkScrollable, + FlexibleConnectedPositionStrategy, + Overlay, + OverlayConfig, + ScrollStrategy +} from '@angular/cdk/overlay'; +import { + AfterContentInit, + AfterViewInit, + ChangeDetectionStrategy, + Component, + Directive, + ElementRef, + EventEmitter, + InjectionToken, + Input, + OnInit, + Output, + TemplateRef, + Type, + ViewChild, + ViewEncapsulation, + booleanAttribute, + inject, + numberAttribute +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { + KbqComponentColors, + KbqPopUp, + KbqPopUpTrigger, + POSITION_TO_CSS_MAP, + PopUpSizes, + PopUpTriggers, + applyPopupMargins +} from '@koobiq/components/core'; +import { defaultOffsetYWithArrow } from '@koobiq/components/popover'; +import { merge } from 'rxjs'; +import { kbqAppSwitcherAnimations } from './app-switcher-animations'; + +@Component({ + selector: 'kbq-app-switcher-component', + templateUrl: './app-switcher.component.html', + preserveWhitespaces: false, + styleUrls: ['./app-switcher.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [kbqAppSwitcherAnimations.state] +}) +export class KbqAppSwitcherComponent extends KbqPopUp implements AfterViewInit { + protected readonly componentColors = KbqComponentColors; + + prefix = 'kbq-app-switcher'; + + trigger: KbqAppSwitcherTrigger; + + isTrapFocus: boolean = false; + + @ViewChild('appSwitcherContent') appSwitcherContent: ElementRef; + @ViewChild('appSwitcher') elementRef: ElementRef; + @ViewChild(CdkTrapFocus) cdkTrapFocus: CdkTrapFocus; + + ngAfterViewInit() { + if (!this.appSwitcherContent) { + return; + } + + this.cdkTrapFocus.focusTrap.focusFirstTabbableElement(); + this.visibleChange.subscribe((state) => { + if (this.offset !== null && state) { + applyPopupMargins( + this.renderer, + this.elementRef.nativeElement, + this.prefix, + `${this.offset!.toString()}px` + ); + } + }); + } + + updateClassMap(placement: string, customClass: string, size: PopUpSizes) { + super.updateClassMap(placement, customClass, { [`${this.prefix}_${size}`]: !!size }); + } + + updateTrapFocus(isTrapFocus: boolean): void { + this.isTrapFocus = isTrapFocus; + } +} + +export const KBQ_APP_SWITCHER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( + 'kbq-app-switcher-scroll-strategy' +); + +/** @docs-private */ +export function kbqAppSwitcherScrollStrategyFactory(overlay: Overlay): () => ScrollStrategy { + return () => overlay.scrollStrategies.reposition({ scrollThrottle: 20 }); +} + +/** @docs-private */ +export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER = { + provide: KBQ_APP_SWITCHER_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: kbqAppSwitcherScrollStrategyFactory +}; + +@Directive({ + selector: '[kbqAppSwitcher]', + exportAs: 'kbqAppSwitcher', + host: { + '[class.kbq-app-switcher_open]': 'isOpen', + '[class.kbq-active]': 'hasClickTrigger && isOpen', + '(keydown)': 'handleKeydown($event)', + '(touchend)': 'handleTouchend()' + } +}) +export class KbqAppSwitcherTrigger + extends KbqPopUpTrigger + implements AfterContentInit, OnInit +{ + protected scrollStrategy: () => ScrollStrategy = inject(KBQ_APP_SWITCHER_SCROLL_STRATEGY); + + // not used + arrow: boolean = false; + customClass: string; + private hasBackdrop: boolean = false; + private size: PopUpSizes = PopUpSizes.Medium; + content: string | TemplateRef; + header: string | TemplateRef; + footer: string | TemplateRef; + + @Input({ transform: booleanAttribute }) + get disabled(): boolean { + return this._disabled; + } + + set disabled(value) { + this._disabled = coerceBooleanProperty(value); + + if (this._disabled) { + this.hide(); + } + } + + trigger: string = `${PopUpTriggers.Click}, ${PopUpTriggers.Keydown}`; + + get hasClickTrigger(): boolean { + return this.trigger.includes(PopUpTriggers.Click); + } + + @Input() backdropClass: string = 'cdk-overlay-transparent-backdrop'; + + @Input({ transform: numberAttribute }) offset: number | null = defaultOffsetYWithArrow; + + @Output('kbqPlacementChange') readonly placementChange = new EventEmitter(); + + @Output('kbqVisibleChange') readonly visibleChange = new EventEmitter(); + + protected originSelector = '.kbq-app-switcher'; + + protected get overlayConfig(): OverlayConfig { + return { + panelClass: 'kbq-app-switcher__panel', + hasBackdrop: this.hasBackdrop, + backdropClass: this.backdropClass + }; + } + + ngOnInit(): void { + super.ngOnInit(); + + this.scrollable + ?.elementScrolled() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(this.hideIfNotInViewPort); + } + + ngAfterContentInit(): void { + if (this.closeOnScroll === null) { + this.scrollDispatcher.scrolled().subscribe((scrollable: CdkScrollable | void) => { + if (!scrollable?.getElementRef().nativeElement.classList.contains('kbq-hide-nested-popup')) return; + + const parentRects = scrollable.getElementRef().nativeElement.getBoundingClientRect(); + const childRects = this.elementRef.nativeElement.getBoundingClientRect(); + + if (childRects.bottom < parentRects.top || childRects.top > parentRects.bottom) { + this.hide(); + } + }); + } + } + + updateData() { + if (!this.instance) return; + + this.instance.header = this.header; + this.instance.content = this.content; + this.instance.arrow = this.arrow; + this.instance.offset = this.offset; + this.instance.footer = this.footer; + + this.instance.updateTrapFocus(this.trigger !== PopUpTriggers.Focus); + + if (this.isOpen) { + this.updatePosition(true); + } + } + + /** Updates the current position. */ + updatePosition(reapplyPosition: boolean = false) { + this.overlayRef = this.createOverlay(); + + const position = (this.overlayRef.getConfig().positionStrategy as FlexibleConnectedPositionStrategy) + .withPositions(this.getAdjustedPositions()) + .withPush(true); + + if (reapplyPosition) { + setTimeout(() => position.reapplyLastPosition()); + } + } + + getOverlayHandleComponentType(): Type { + return KbqAppSwitcherComponent; + } + + updateClassMap(newPlacement: string = this.placement) { + if (!this.instance) return; + + this.instance.updateClassMap(POSITION_TO_CSS_MAP[newPlacement], this.customClass, this.size); + this.instance.markForCheck(); + } + + closingActions() { + return merge( + this.overlayRef!.outsidePointerEvents(), + this.overlayRef!.backdropClick(), + this.scrollDispatcher.scrolled() + ); + } + + private hideIfNotInViewPort = () => { + if (!this.scrollable) return; + + const rect = this.elementRef.nativeElement.getBoundingClientRect(); + const containerRect = this.scrollable.getElementRef().nativeElement.getBoundingClientRect(); + + if ( + !( + rect.bottom >= containerRect.top && + rect.right >= containerRect.left && + rect.top <= containerRect.bottom && + rect.left <= containerRect.right + ) + ) { + this.hide(); + } + }; +} diff --git a/packages/components/app-switcher/app-switcher.en.md b/packages/components/app-switcher/app-switcher.en.md new file mode 100644 index 000000000..2faef3848 --- /dev/null +++ b/packages/components/app-switcher/app-switcher.en.md @@ -0,0 +1,5 @@ +🚧 **Documentation in progress** 🚧 + +Unfortunately, the documentation for this section is not ready yet. We are actively working on its creation and plan to add it soon. + +If you would like to contribute to the documentation or have any questions, please feel free to [open an issue](https://github.com/koobiq/angular-components/issues) in our GitHub repository. diff --git a/packages/components/app-switcher/app-switcher.module.ts b/packages/components/app-switcher/app-switcher.module.ts new file mode 100644 index 000000000..d277dcfb8 --- /dev/null +++ b/packages/components/app-switcher/app-switcher.module.ts @@ -0,0 +1,43 @@ +import { + A11yModule, + ConfigurableFocusTrapFactory, + FOCUS_TRAP_INERT_STRATEGY, + FocusTrapFactory +} from '@angular/cdk/a11y'; +import { CdkObserveContent } from '@angular/cdk/observers'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { NgClass, NgTemplateOutlet } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { KbqButtonModule } from '@koobiq/components/button'; +import { EmptyFocusTrapStrategy } from '@koobiq/components/core'; +import { KbqIconModule } from '@koobiq/components/icon'; +import { + KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER, + KbqAppSwitcherComponent, + KbqAppSwitcherTrigger +} from './app-switcher.component'; + +@NgModule({ + declarations: [ + KbqAppSwitcherComponent, + KbqAppSwitcherTrigger + ], + exports: [ + KbqAppSwitcherComponent, + KbqAppSwitcherTrigger + ], + imports: [ + OverlayModule, + KbqButtonModule, + A11yModule, + KbqIconModule, + CdkObserveContent, + NgClass, + NgTemplateOutlet + ], + providers: [ + KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER, + { provide: FocusTrapFactory, useClass: ConfigurableFocusTrapFactory }, + { provide: FOCUS_TRAP_INERT_STRATEGY, useClass: EmptyFocusTrapStrategy }] +}) +export class KbqAppSwitcherModule {} diff --git a/packages/components/app-switcher/app-switcher.ru.md b/packages/components/app-switcher/app-switcher.ru.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/components/app-switcher/app-switcher.scss b/packages/components/app-switcher/app-switcher.scss new file mode 100644 index 000000000..28013a654 --- /dev/null +++ b/packages/components/app-switcher/app-switcher.scss @@ -0,0 +1,8 @@ +@use './app-switcher-theme' as *; + +.kbq-app-switcher { + +} + +@include kbq-app-switcher-theme(); +@include kbq-app-switcher-typography(); diff --git a/packages/components/app-switcher/app-switcher.spec.ts b/packages/components/app-switcher/app-switcher.spec.ts new file mode 100644 index 000000000..d19f6f111 --- /dev/null +++ b/packages/components/app-switcher/app-switcher.spec.ts @@ -0,0 +1,66 @@ +import { OverlayContainer } from '@angular/cdk/overlay'; +import { Component, DebugElement, Provider, Type } from '@angular/core'; +import { ComponentFixture, TestBed, fakeAsync, inject } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler'; +import { TestScheduler } from 'rxjs/testing'; +import { KbqAppSwitcherModule } from './app-switcher.module'; + + +describe('KbqAppSwitcher', () => { + let fixture: ComponentFixture; + let componentInstance: AppSwitcherSimple; + let debugElement: DebugElement; + let overlayContainer: OverlayContainer; + let overlayContainerElement: HTMLElement; + let testScheduler: TestScheduler; + + const createComponent = (component: Type, providers: Provider[] = []): ComponentFixture => { + TestBed.configureTestingModule({ + imports: [component, NoopAnimationsModule], + providers: [ + { provide: AsyncScheduler, useValue: testScheduler }, + ...providers + ] + }); + const fixture = TestBed.createComponent(component); + + fixture.autoDetectChanges(); + + return fixture; + }; + + describe('Check test cases', () => { + beforeEach(() => { + testScheduler = new TestScheduler((act, exp) => expect(exp).toEqual(act)); + fixture = createComponent(AppSwitcherSimple); + componentInstance = fixture.componentInstance; + debugElement = fixture.debugElement; + }); + + beforeEach(inject([OverlayContainer], (oc: OverlayContainer) => { + overlayContainer = oc; + overlayContainerElement = oc.getContainerElement(); + })); + + afterEach(() => { + overlayContainer.ngOnDestroy(); + }); + + it('kbqTrigger = hover', fakeAsync(() => { + expect(true).toBe(true); + })); + }); +}); + +@Component({ + standalone: true, + selector: 'app-switcher-simple', + template: ` + + `, + imports: [ + KbqAppSwitcherModule + ] +}) +export class AppSwitcherSimple {} diff --git a/packages/components/app-switcher/examples.app-switcher.en.md b/packages/components/app-switcher/examples.app-switcher.en.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/packages/components/app-switcher/examples.app-switcher.en.md @@ -0,0 +1 @@ + diff --git a/packages/components/app-switcher/examples.app-switcher.ru.md b/packages/components/app-switcher/examples.app-switcher.ru.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/components/app-switcher/index.ts b/packages/components/app-switcher/index.ts new file mode 100644 index 000000000..7e1a213e3 --- /dev/null +++ b/packages/components/app-switcher/index.ts @@ -0,0 +1 @@ +export * from './public-api'; diff --git a/packages/components/app-switcher/ng-package.json b/packages/components/app-switcher/ng-package.json new file mode 100644 index 000000000..bebf62dcb --- /dev/null +++ b/packages/components/app-switcher/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "index.ts" + } +} diff --git a/packages/components/app-switcher/public-api.ts b/packages/components/app-switcher/public-api.ts new file mode 100644 index 000000000..6e5c9dbdd --- /dev/null +++ b/packages/components/app-switcher/public-api.ts @@ -0,0 +1,3 @@ +export * from './app-switcher-animations'; +export * from './app-switcher.component'; +export * from './app-switcher.module'; diff --git a/packages/components/popover/popover.component.ts b/packages/components/popover/popover.component.ts index ce5c5e8c7..206f27d79 100644 --- a/packages/components/popover/popover.component.ts +++ b/packages/components/popover/popover.component.ts @@ -42,7 +42,7 @@ import { NEVER, fromEvent, merge } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { kbqPopoverAnimations } from './popover-animations'; -const defaultOffsetYWithArrow = 8; +export const defaultOffsetYWithArrow = 8; @Component({ selector: 'kbq-popover-component', diff --git a/tsconfig.json b/tsconfig.json index 4f9a774de..fc6f88d69 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,7 @@ "@koobiq/components/actions-panel": ["packages/components/actions-panel/index.ts"], "@koobiq/components/alert": ["packages/components/alert/index.ts"], "@koobiq/components/autocomplete": ["packages/components/autocomplete/index.ts"], + "@koobiq/components/app-switcher": ["packages/components/app-switcher/index.ts"], "@koobiq/components/badge": ["packages/components/badge/index.ts"], "@koobiq/components/button": ["packages/components/button/index.ts"], "@koobiq/components/breadcrumbs": ["packages/components/breadcrumbs/index.ts"], From 60526152bedb90e80388b1f8148f2b21ecfa1492 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Fri, 5 Sep 2025 17:53:17 +0300 Subject: [PATCH 02/17] feat: save --- .../components-dev/app-switcher/module.ts | 2 +- .../app-switcher/_app-switcher-theme.scss | 2 +- .../app-switcher/app-switcher-tree.html | 0 .../app-switcher/app-switcher-tree.scss | 21 +++++++++++ .../app-switcher/app-switcher-tree.ts | 17 +++++++++ .../app-switcher/app-switcher.component.html | 11 ------ .../components/app-switcher/app-switcher.html | 16 +++++++++ .../app-switcher/app-switcher.module.ts | 17 ++++++--- .../components/app-switcher/app-switcher.scss | 13 +++++++ ...-switcher.component.ts => app-switcher.ts} | 36 ++++++++++++++----- .../components/app-switcher/public-api.ts | 3 +- 11 files changed, 110 insertions(+), 28 deletions(-) create mode 100644 packages/components/app-switcher/app-switcher-tree.html create mode 100644 packages/components/app-switcher/app-switcher-tree.scss create mode 100644 packages/components/app-switcher/app-switcher-tree.ts delete mode 100644 packages/components/app-switcher/app-switcher.component.html create mode 100644 packages/components/app-switcher/app-switcher.html rename packages/components/app-switcher/{app-switcher.component.ts => app-switcher.ts} (88%) diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index 3548e885b..5b3eb4f4e 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -8,7 +8,7 @@ import { PopoverExamplesModule } from 'packages/docs-examples/components/popover @Component({ standalone: true, - imports: [PopoverExamplesModule, KbqButtonModule, KbqIcon], + imports: [PopoverExamplesModule, KbqButtonModule, KbqIcon, KbqAppSwitcherModule], selector: 'dev-examples', template: ` + + + + + + + + + + + + + + + + + + + + + } + + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index 3bff3ecf3..b9817cbc0 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -40,6 +40,7 @@ import { applyPopupMargins } from '@koobiq/components/core'; import { KbqDividerModule } from '@koobiq/components/divider'; +import { KbqDropdownModule } from '@koobiq/components/dropdown'; import { KbqFormFieldModule } from '@koobiq/components/form-field'; import { KbqIcon } from '@koobiq/components/icon'; import { KbqInputModule } from '@koobiq/components/input'; @@ -142,7 +143,8 @@ export const DATA_OBJECT = { KbqIcon, KbqTreeModule, KbqAppSwitcherTreeOption, - KbqAppSwitcherTreeNodePadding + KbqAppSwitcherTreeNodePadding, + KbqDropdownModule ], animations: [kbqAppSwitcherAnimations.state] }) diff --git a/packages/components/dropdown/dropdown.component.ts b/packages/components/dropdown/dropdown.component.ts index 45c90adc5..eadd01dce 100644 --- a/packages/components/dropdown/dropdown.component.ts +++ b/packages/components/dropdown/dropdown.component.ts @@ -9,6 +9,7 @@ import { Component, ContentChild, ContentChildren, + Directive, ElementRef, EventEmitter, Inject, @@ -39,6 +40,12 @@ import { KbqDropdownPanel } from './dropdown.types'; +@Directive({ + standalone: true, + selector: '[kbqDropdownStaticContent]' +}) +export class kbqDropdownStaticContent {} + @Component({ selector: 'kbq-dropdown', exportAs: 'kbqDropdown', diff --git a/packages/components/dropdown/dropdown.html b/packages/components/dropdown/dropdown.html index 5eec8b339..392ad50f9 100644 --- a/packages/components/dropdown/dropdown.html +++ b/packages/components/dropdown/dropdown.html @@ -16,3 +16,5 @@ + + diff --git a/packages/components/dropdown/dropdown.module.ts b/packages/components/dropdown/dropdown.module.ts index 16e213bd1..e3844f2bb 100644 --- a/packages/components/dropdown/dropdown.module.ts +++ b/packages/components/dropdown/dropdown.module.ts @@ -5,19 +5,21 @@ import { KbqIconModule } from '@koobiq/components/icon'; import { KbqDropdownContent } from './dropdown-content.directive'; import { KbqDropdownItem } from './dropdown-item.component'; import { KBQ_DROPDOWN_SCROLL_STRATEGY_FACTORY_PROVIDER, KbqDropdownTrigger } from './dropdown-trigger.directive'; -import { KbqDropdown } from './dropdown.component'; +import { KbqDropdown, kbqDropdownStaticContent } from './dropdown.component'; @NgModule({ imports: [ OverlayModule, KbqIconModule, - NgClass + NgClass, + kbqDropdownStaticContent ], exports: [ KbqDropdown, KbqDropdownItem, KbqDropdownTrigger, - KbqDropdownContent + KbqDropdownContent, + kbqDropdownStaticContent ], declarations: [ KbqDropdown, From 5f7352eda22fa08b00da4c5953b8c0f8190e60a4 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Tue, 16 Sep 2025 18:14:04 +0300 Subject: [PATCH 07/17] feat: dropdown styles + logic --- .../app-switcher/_app-switcher-theme.scss | 11 + .../components/app-switcher/app-switcher.html | 74 +++--- .../components/app-switcher/app-switcher.scss | 19 ++ .../components/app-switcher/app-switcher.ts | 251 ++++++++++++++++++ 4 files changed, 316 insertions(+), 39 deletions(-) diff --git a/packages/components/app-switcher/_app-switcher-theme.scss b/packages/components/app-switcher/_app-switcher-theme.scss index 616de5028..48e1579cd 100644 --- a/packages/components/app-switcher/_app-switcher-theme.scss +++ b/packages/components/app-switcher/_app-switcher-theme.scss @@ -8,6 +8,17 @@ color: var(--kbq-foreground-contrast-secondary); } } + + .kbq-app-switcher-sites { + & .kbq-dropdown-item { + text-decoration: none; + + & .kbq-pressed { + background: var(--kbq-states-background-transparent-active) + + } + } + } } @mixin kbq-app-switcher-typography() { diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index d3feedf2f..637fb1d68 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -37,49 +37,45 @@
Другие площадки
- -
- - - - - - - - - - - - - - - - - - - - + +
+ @for (site of sites; track site) { + + {{ site.name }} + @if (site.main) { + Главная + } + + }
} - - - - - - - - - - - - - - - - + + @for (app of activeSite?.apps; track app) { + @if (app.apps) { + + {{ app.name }} + + } @else { + {{ app.name }} + } + } + - + + @for (app of activeApp?.apps; track app) { + {{ app.name }} + } diff --git a/packages/components/app-switcher/app-switcher.scss b/packages/components/app-switcher/app-switcher.scss index 73221d477..b2f31ea4a 100644 --- a/packages/components/app-switcher/app-switcher.scss +++ b/packages/components/app-switcher/app-switcher.scss @@ -29,5 +29,24 @@ justify-content: space-between; } +.kbq-app-switcher__sites-container { +} + +.kbq-app-switcher-sites { + & .kbq-app-switcher__sites_main { + & .kbq-dropdown-item__text { + justify-content: space-between; + display: flex; + flex-direction: row; + } + + & .kbq-badge { + align-self: center; + } + } + +} + + @include kbq-app-switcher-theme(); @include kbq-app-switcher-typography(); diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index b9817cbc0..b850c4731 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -161,6 +161,9 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { isTrapFocus: boolean = false; + activeSite; + activeApp; + @ViewChild('appSwitcherContent') appSwitcherContent: ElementRef; @ViewChild('appSwitcher') elementRef: ElementRef; @ViewChild(CdkTrapFocus) cdkTrapFocus: CdkTrapFocus; @@ -214,6 +217,254 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { modelValue: any = ''; + sites = [ + { + name: 'ЦФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall' + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'СЗФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'ЮФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall' + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'СКФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ], + main: true + }, + { + name: 'ПФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall' + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'УФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'СФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall' + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'ДФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + } + ]; + hasChild(_: number, nodeData: FileFlatNode) { return nodeData.expandable; } From 1ccf345de0d1500a5bf4b3d98d39b4aadc02ccb0 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Wed, 17 Sep 2025 17:02:05 +0300 Subject: [PATCH 08/17] feat: kbq-app-switcher-dropdown-app & kbq-app-switcher-dropdown-site --- .../components-dev/app-switcher/module.ts | 316 +++++++++++++++++- .../components-dev/app-switcher/template.html | 69 +++- .../app-switcher/_app-switcher-theme.scss | 8 - .../app-switcher-dropdown-app.scss | 85 +++++ .../app-switcher/app-switcher-dropdown-app.ts | 54 +++ .../app-switcher-dropdown-site.scss | 55 +++ .../app-switcher-dropdown-site.ts | 44 +++ .../components/app-switcher/app-switcher.html | 62 ++-- .../components/app-switcher/app-switcher.scss | 17 +- .../components/app-switcher/app-switcher.ts | 82 ++++- .../components/app-switcher/public-api.ts | 3 + 11 files changed, 712 insertions(+), 83 deletions(-) create mode 100644 packages/components/app-switcher/app-switcher-dropdown-app.scss create mode 100644 packages/components/app-switcher/app-switcher-dropdown-app.ts create mode 100644 packages/components/app-switcher/app-switcher-dropdown-site.scss create mode 100644 packages/components/app-switcher/app-switcher-dropdown-site.ts diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index 032f9a2bc..3096dcafb 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -6,13 +6,16 @@ import { DATA_OBJECT, FileFlatNode, FileNode, + KbqAppSwitcherDropdownApp, + KbqAppSwitcherDropdownSite, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; +import { KbqBadgeModule } from '@koobiq/components/badge'; import { KbqButtonModule } from '@koobiq/components/button'; import { KbqComponentColors } from '@koobiq/components/core'; import { KbqDividerModule } from '@koobiq/components/divider'; import { KbqDropdownModule } from '@koobiq/components/dropdown'; -import { KbqIcon, KbqIconButton } from '@koobiq/components/icon'; +import { KbqIcon } from '@koobiq/components/icon'; import { FlatTreeControl, KbqTreeFlatDataSource, KbqTreeFlattener, KbqTreeModule } from '@koobiq/components/tree'; import { PopoverExamplesModule } from 'packages/docs-examples/components/popover'; @@ -40,10 +43,11 @@ class DevExamples {} DevExamples, KbqAppSwitcherModule, KbqTreeModule, - KbqIconButton, - KbqIcon, KbqDropdownModule, - KbqDividerModule + KbqDividerModule, + KbqBadgeModule, + KbqAppSwitcherDropdownApp, + KbqAppSwitcherDropdownSite ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush @@ -57,6 +61,310 @@ export class DevApp { modelValue: any = ''; + sites = [ + { + name: 'ЦФО', + apps: [ + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + apps: [ + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + } + ] + }, + { + name: 'Name', + caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + apps: [ + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + } + ] + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + apps: [ + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + } + ] + } + ] + }, + { + name: 'СЗФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + icon: '' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'ЮФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall' + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'Южный Суверенный Федеральный Округ ФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ], + main: true + }, + { + name: 'ПФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall' + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'УФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'СФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall' + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + }, + { + name: 'ДФО', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001' + }, + { + name: 'CryptoWall', + apps: [ + { + name: 'App Instance 1', + caption: 'Instance Alias One' + }, + { + name: 'App Instance 2' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four' + } + ] + }, + { + name: 'Phantom Gate' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals' + }, + { + name: 'Zero Trace' + } + ] + } + ]; + + activeSite; + activeApp; + constructor() { this.treeFlattener = new KbqTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); diff --git a/packages/components-dev/app-switcher/template.html b/packages/components-dev/app-switcher/template.html index 385377446..b3794bd95 100644 --- a/packages/components-dev/app-switcher/template.html +++ b/packages/components-dev/app-switcher/template.html @@ -4,21 +4,66 @@

-{{ modelValue }} + - - - {{ treeControl.getViewValue(node) }} - + + + + - - - - + + + + - {{ treeControl.getViewValue(node) }} - - + + + + +
+
+
Другие площадки
+
+ + +
+ @for (site of sites; track site) { + + } +
+
+
+ + + @for (app of activeSite?.apps; track app) { + @if (app.apps) { + + } @else { + + } + } + + + + @for (app of activeApp?.apps; track app) { + + } +

diff --git a/packages/components/app-switcher/_app-switcher-theme.scss b/packages/components/app-switcher/_app-switcher-theme.scss index 48e1579cd..8333d681d 100644 --- a/packages/components/app-switcher/_app-switcher-theme.scss +++ b/packages/components/app-switcher/_app-switcher-theme.scss @@ -10,14 +10,6 @@ } .kbq-app-switcher-sites { - & .kbq-dropdown-item { - text-decoration: none; - - & .kbq-pressed { - background: var(--kbq-states-background-transparent-active) - - } - } } } diff --git a/packages/components/app-switcher/app-switcher-dropdown-app.scss b/packages/components/app-switcher/app-switcher-dropdown-app.scss new file mode 100644 index 000000000..4db1ce68d --- /dev/null +++ b/packages/components/app-switcher/app-switcher-dropdown-app.scss @@ -0,0 +1,85 @@ +@use '../core/styles/common'; +@use '../core/styles/common/tokens'; + +.kbq-app-switcher-dropdown-app { + display: flex; + flex-direction: row; + + width: 100%; + min-height: 50px; + + border-radius: var(--kbq-size-xs); + + @include common.user-select(none); + + text-decoration: none; + box-sizing: border-box; + outline: none; + -webkit-tap-highlight-color: transparent; + cursor: pointer; + + padding: var(--kbq-size-xxs) var(--kbq-size-m); + + & .kbq-app-switcher-dropdown-app__icon { + padding-top: var(--kbq-size-s); + padding-right: var(--kbq-size-m); + } + + & .kbq-app-switcher-dropdown-app__container { + width: 100%; + + align-self: center; + + & .kbq-app-switcher-dropdown-app__name { + display: flex; + flex-direction: row; + + justify-content: space-between; + } + + & .kbq-app-switcher-dropdown-app__caption { + margin-left: var(--kbq-size-3xs); + } + } + + & .kbq-dropdown-item__caption { + padding-top: var(--kbq-size-3xs); + } + + & .kbq-app-switcher-dropdown-item-trigger__icon { + margin-top: var(--kbq-size-3xs); + } +} + +@mixin kbq-app-switcher-dropdown-app-theme() { + .kbq-app-switcher-dropdown-app { + background: var(--kbq-list-default-container-background); + color: var(--kbq-list-default-text-color); + + &.kbq-dropdown-item_highlighted, + &:hover { + background: var(--kbq-states-background-transparent-active); + } + + &.cdk-keyboard-focused { + border-color: var(--kbq-list-states-focused-focus-outline-color); + } + + .kbq-app-switcher-dropdown-app__caption { + color: var(--kbq-dropdown-item-caption-color); + } + } +} + +@mixin kbq-app-switcher-dropdown-app-typography() { + .kbq-app-switcher-dropdown-app { + @include tokens.kbq-typography-level-to-styles-css-variables(typography, text-normal); + + .kbq-app-switcher-dropdown-app__caption { + @include tokens.kbq-typography-level-to-styles-css-variables(typography, text-compact); + } + } +} + +@include kbq-app-switcher-dropdown-app-theme(); +@include kbq-app-switcher-dropdown-app-typography(); diff --git a/packages/components/app-switcher/app-switcher-dropdown-app.ts b/packages/components/app-switcher/app-switcher-dropdown-app.ts new file mode 100644 index 000000000..64a81382e --- /dev/null +++ b/packages/components/app-switcher/app-switcher-dropdown-app.ts @@ -0,0 +1,54 @@ +import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; +import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; +import { KbqDropdownItem } from '@koobiq/components/dropdown'; +import { KbqIcon } from '@koobiq/components/icon'; + +@Component({ + standalone: true, + selector: '[kbq-app-switcher-dropdown-app]', + exportAs: 'kbqAppSwitcherDropdownApp', + template: ` + + + + + + + + +
+
+ {{ app.name }} + + @if (isNested) { + + } +
+ @if (app.caption) { +
{{ app.caption }}
+ } +
+ `, + styleUrls: ['app-switcher-dropdown-app.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { + class: 'kbq-app-switcher-dropdown-app', + '[class.kbq-dropdown-item]': 'false' + }, + imports: [ + KbqIcon + ], + providers: [ + { provide: KBQ_TITLE_TEXT_REF, useExisting: KbqAppSwitcherDropdownApp }, + { provide: KbqDropdownItem, useExisting: KbqAppSwitcherDropdownApp } + ] +}) +export class KbqAppSwitcherDropdownApp extends KbqDropdownItem { + @Input('kbq-app-switcher-dropdown-app') app; +} diff --git a/packages/components/app-switcher/app-switcher-dropdown-site.scss b/packages/components/app-switcher/app-switcher-dropdown-site.scss new file mode 100644 index 000000000..5004063e4 --- /dev/null +++ b/packages/components/app-switcher/app-switcher-dropdown-site.scss @@ -0,0 +1,55 @@ +@use '../core/styles/common'; +@use '../core/styles/common/tokens'; + +.kbq-app-switcher-dropdown-site { + display: flex; + flex-direction: row; + + justify-content: space-between; + + width: 100%; + min-height: var(--kbq-size-3xl); + + text-decoration: none; + + border-radius: var(--kbq-size-xs); + + @include common.user-select(none); + @include common.kbq-list-item-base(); + + &:not(.kbq-disabled) { + cursor: pointer; + } + + & .kbq-app-switcher-dropdown-item-trigger__icon, + & .kbq-badge { + margin-top: var(--kbq-size-3xs); + + align-self: flex-start; + } +} + +@mixin kbq-app-switcher-dropdown-site-theme() { + .kbq-app-switcher-dropdown-site { + background: var(--kbq-list-default-container-background); + color: var(--kbq-list-default-text-color); + + &.kbq-dropdown-item_highlighted, + &:hover { + background: var(--kbq-states-background-transparent-active); + } + + &.cdk-keyboard-focused { + border-color: var(--kbq-list-states-focused-focus-outline-color); + } + } +} + +@mixin kbq-app-switcher-dropdown-site-typography() { + .kbq-app-switcher-dropdown-site { + @include tokens.kbq-typography-level-to-styles-css-variables(typography, text-normal); + } +} + +@include kbq-app-switcher-dropdown-site-theme(); +@include kbq-app-switcher-dropdown-site-typography(); diff --git a/packages/components/app-switcher/app-switcher-dropdown-site.ts b/packages/components/app-switcher/app-switcher-dropdown-site.ts new file mode 100644 index 000000000..f07e59bea --- /dev/null +++ b/packages/components/app-switcher/app-switcher-dropdown-site.ts @@ -0,0 +1,44 @@ +import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; +import { KbqBadgeModule } from '@koobiq/components/badge'; +import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; +import { KbqDropdownItem } from '@koobiq/components/dropdown'; +import { KbqIcon } from '@koobiq/components/icon'; + +@Component({ + standalone: true, + selector: '[kbq-app-switcher-dropdown-site]', + exportAs: 'kbqAppSwitcherDropdownSite', + template: ` + {{ site.name }} + + @if (site.main) { + Главная + } + + @if (isNested) { + + } + `, + styleUrls: ['app-switcher-dropdown-site.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { + class: 'kbq-app-switcher-dropdown-site', + '[class.kbq-dropdown-item]': 'false' + }, + imports: [ + KbqIcon, + KbqBadgeModule + ], + providers: [ + { provide: KBQ_TITLE_TEXT_REF, useExisting: KbqAppSwitcherDropdownSite }, + { provide: KbqDropdownItem, useExisting: KbqAppSwitcherDropdownSite } + ] +}) +export class KbqAppSwitcherDropdownSite extends KbqDropdownItem { + @Input('kbq-app-switcher-dropdown-site') site; +} diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index 637fb1d68..729fdf04a 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -1,5 +1,5 @@ @if (withSearch) { -
+
@@ -10,7 +10,7 @@ } -
+
Название площадки
Главная @@ -38,44 +38,42 @@
-
+
@for (site of sites; track site) { - - {{ site.name }} - @if (site.main) { - Главная - } - + > }
-} - - @for (app of activeSite?.apps; track app) { - @if (app.apps) { - - {{ app.name }} - - } @else { - {{ app.name }} + + @for (app of activeSite?.apps; track app) { + @if (app.apps) { + + } @else { + + } } - } - + - - @for (app of activeApp?.apps; track app) { - {{ app.name }} - } - + + @for (app of activeApp?.apps; track app) { + + } + +} diff --git a/packages/components/app-switcher/app-switcher.scss b/packages/components/app-switcher/app-switcher.scss index b2f31ea4a..2561527ba 100644 --- a/packages/components/app-switcher/app-switcher.scss +++ b/packages/components/app-switcher/app-switcher.scss @@ -30,23 +30,8 @@ } .kbq-app-switcher__sites-container { + padding: var(--kbq-size-xxs); } -.kbq-app-switcher-sites { - & .kbq-app-switcher__sites_main { - & .kbq-dropdown-item__text { - justify-content: space-between; - display: flex; - flex-direction: row; - } - - & .kbq-badge { - align-self: center; - } - } - -} - - @include kbq-app-switcher-theme(); @include kbq-app-switcher-typography(); diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index b850c4731..4f0237dde 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -40,7 +40,7 @@ import { applyPopupMargins } from '@koobiq/components/core'; import { KbqDividerModule } from '@koobiq/components/divider'; -import { KbqDropdownModule } from '@koobiq/components/dropdown'; +import { KbqDropdownModule, KbqDropdownTrigger } from '@koobiq/components/dropdown'; import { KbqFormFieldModule } from '@koobiq/components/form-field'; import { KbqIcon } from '@koobiq/components/icon'; import { KbqInputModule } from '@koobiq/components/input'; @@ -48,6 +48,8 @@ import { defaultOffsetYWithArrow } from '@koobiq/components/popover'; import { FlatTreeControl, KbqTreeFlatDataSource, KbqTreeFlattener, KbqTreeModule } from '@koobiq/components/tree'; import { merge } from 'rxjs'; import { kbqAppSwitcherAnimations } from './app-switcher-animations'; +import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; +import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; import { KbqAppSwitcherTree } from './app-switcher-tree'; import { KbqAppSwitcherTreeNodePadding, KbqAppSwitcherTreeOption } from './app-switcher-tree-option'; @@ -144,7 +146,9 @@ export const DATA_OBJECT = { KbqTreeModule, KbqAppSwitcherTreeOption, KbqAppSwitcherTreeNodePadding, - KbqDropdownModule + KbqDropdownModule, + KbqAppSwitcherDropdownApp, + KbqAppSwitcherDropdownSite ], animations: [kbqAppSwitcherAnimations.state] }) @@ -166,6 +170,7 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { @ViewChild('appSwitcherContent') appSwitcherContent: ElementRef; @ViewChild('appSwitcher') elementRef: ElementRef; + @ViewChild('otherSites') otherSites: KbqDropdownTrigger; @ViewChild(CdkTrapFocus) cdkTrapFocus: CdkTrapFocus; constructor() { @@ -222,21 +227,75 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { name: 'ЦФО', apps: [ { - name: 'Byte Sentinel', - caption: 'Byte 001' + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' }, { - name: 'CryptoWall' + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' }, { - name: 'Phantom Gate' + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' }, { - name: 'SentraLock', - caption: 'Lock-sentral-urals' + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + apps: [ + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + } + ] }, { - name: 'Zero Trace' + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + apps: [ + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + } + ] + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + apps: [ + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + } + ] } ] }, @@ -245,7 +304,8 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { apps: [ { name: 'Byte Sentinel', - caption: 'Byte 001' + caption: 'Byte 001', + icon: '' }, { name: 'CryptoWall', @@ -302,7 +362,7 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { ] }, { - name: 'СКФО', + name: 'Южный Суверенный Федеральный Округ ФО', apps: [ { name: 'Byte Sentinel', diff --git a/packages/components/app-switcher/public-api.ts b/packages/components/app-switcher/public-api.ts index 56a176dea..d3d5a6232 100644 --- a/packages/components/app-switcher/public-api.ts +++ b/packages/components/app-switcher/public-api.ts @@ -1,4 +1,7 @@ export * from './app-switcher'; export * from './app-switcher-animations'; +export * from './app-switcher-dropdown-app'; +export * from './app-switcher-dropdown-site'; export * from './app-switcher-tree'; +export * from './app-switcher-tree-option'; export * from './app-switcher.module'; From 3c14bcaf8c949909a7f6d229c56d15e1b86f53d7 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Thu, 18 Sep 2025 16:46:53 +0300 Subject: [PATCH 09/17] feat: refactor + replace tree on dropdown --- .../components-dev/app-switcher/module.ts | 6 +- .../components-dev/app-switcher/template.html | 61 ------ .../app-switcher/_app-switcher-theme.scss | 4 - .../app-switcher/app-switcher-app.scss | 187 ++++++++++++++++++ .../app-switcher/app-switcher-app.ts | 60 ++++++ .../app-switcher-tree-option.html | 27 --- .../app-switcher-tree-option.scss | 159 --------------- .../app-switcher/app-switcher-tree-option.ts | 87 -------- .../app-switcher/app-switcher-tree.scss | 4 - .../app-switcher/app-switcher-tree.ts | 43 ---- .../components/app-switcher/app-switcher.html | 24 +-- .../app-switcher/app-switcher.module.ts | 16 +- .../components/app-switcher/app-switcher.scss | 21 +- .../components/app-switcher/app-switcher.ts | 28 ++- .../components/app-switcher/public-api.ts | 3 +- 15 files changed, 306 insertions(+), 424 deletions(-) create mode 100644 packages/components/app-switcher/app-switcher-app.scss create mode 100644 packages/components/app-switcher/app-switcher-app.ts delete mode 100644 packages/components/app-switcher/app-switcher-tree-option.html delete mode 100644 packages/components/app-switcher/app-switcher-tree-option.scss delete mode 100644 packages/components/app-switcher/app-switcher-tree-option.ts delete mode 100644 packages/components/app-switcher/app-switcher-tree.scss delete mode 100644 packages/components/app-switcher/app-switcher-tree.ts diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index 3096dcafb..99bc19c6c 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -6,8 +6,6 @@ import { DATA_OBJECT, FileFlatNode, FileNode, - KbqAppSwitcherDropdownApp, - KbqAppSwitcherDropdownSite, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; import { KbqBadgeModule } from '@koobiq/components/badge'; @@ -45,9 +43,7 @@ class DevExamples {} KbqTreeModule, KbqDropdownModule, KbqDividerModule, - KbqBadgeModule, - KbqAppSwitcherDropdownApp, - KbqAppSwitcherDropdownSite + KbqBadgeModule ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush diff --git a/packages/components-dev/app-switcher/template.html b/packages/components-dev/app-switcher/template.html index b3794bd95..3953e193e 100644 --- a/packages/components-dev/app-switcher/template.html +++ b/packages/components-dev/app-switcher/template.html @@ -4,67 +4,6 @@

- - - - - - - - - - - - - - - - -
-
-
Другие площадки
-
- - -
- @for (site of sites; track site) { - - } -
-
-
- - - @for (app of activeSite?.apps; track app) { - @if (app.apps) { - - } @else { - - } - } - - - - @for (app of activeApp?.apps; track app) { - - } - -


diff --git a/packages/components/app-switcher/_app-switcher-theme.scss b/packages/components/app-switcher/_app-switcher-theme.scss index 8333d681d..50a47c0b6 100644 --- a/packages/components/app-switcher/_app-switcher-theme.scss +++ b/packages/components/app-switcher/_app-switcher-theme.scss @@ -29,8 +29,4 @@ .kbq-app-switch-tree-option__text { @include kbq-typography-level-to-styles-css-variables(typography, text-normal-medium); } - - .kbq-app-switch-tree-option__alias { - @include kbq-typography-level-to-styles-css-variables(typography, text-compact); - } } diff --git a/packages/components/app-switcher/app-switcher-app.scss b/packages/components/app-switcher/app-switcher-app.scss new file mode 100644 index 000000000..da11d0042 --- /dev/null +++ b/packages/components/app-switcher/app-switcher-app.scss @@ -0,0 +1,187 @@ +@use '../core/styles/common'; +@use '../core/styles/common/tokens'; + +.kbq-app-switcher-app { + position: relative; + + display: flex; + flex-direction: row; + + max-width: 100%; + min-height: 38px; + + border-radius: var(--kbq-size-s); + + padding: var(--kbq-size-xxs) var(--kbq-size-s) var(--kbq-size-xs) var(--kbq-size-m); + + @include common.user-select(none); + + text-decoration: none; + box-sizing: border-box; + outline: none; + -webkit-tap-highlight-color: transparent; + cursor: pointer; + + &.kbq-app-switcher-site_nested { + margin-left: var(--kbq-size-xxl); + + border-top-left-radius: unset; + border-bottom-left-radius: unset; + + &:after { + content: ''; + + position: absolute; + left: -1px; + top: 0; + + height: 100%; + + border-left-width: 1px; + border-left-style: solid; + } + + &:has(+ :not(.kbq-app-switcher-site_nested)), + &:last-child { + border-bottom-left-radius: var(--kbq-size-s); + + &:after { + top: 0; + + width: var(--kbq-size-m); + height: var(--kbq-size-xxl); + + border-bottom-left-radius: var(--kbq-size-s); + border-bottom-width: 1px; + border-bottom-style: solid; + } + } + } +} + +.kbq-app-switcher-app__toggle { + position: absolute; + + display: none; + align-items: center; + justify-content: center; + + top: var(--kbq-size-m); + left: var(--kbq-size-m); + + width: var(--kbq-size-xxl); + height: var(--kbq-size-xxl); + + border-radius: var(--kbq-size-xxs); + + + & .kbq-icon { + transform: rotate(0deg); + } + + &.kbq-expanded { + & .kbq-icon { + transform: rotate(180deg); + } + } +} + +.kbq-app-switcher-app__icon { + padding-top: var(--kbq-size-s); + padding-right: var(--kbq-size-m); +} + +.kbq-app-switcher-app__container { + max-width: 100%; + min-width: 0; + + align-self: center; + + & .kbq-app-switcher-app__name { + @include common.kbq-truncate-line(); + } + + & .kbq-app-switcher-app__caption { + margin-left: var(--kbq-size-3xs); + + @include common.kbq-truncate-line(); + } +} + +@mixin kbq-app-switcher-app-theme() { + + .kbq-app-switcher-site_nested:after { + border-left-color: var(--kbq-line-contrast-less); + border-bottom-color: var(--kbq-line-contrast-less); + } + + .kbq-app-switcher-app__name { + } + + .kbq-app-switcher-app__caption { + color: var(--kbq-foreground-contrast-secondary); + } + + .kbq-app-switcher-app { + &:hover, + &.kbq-hover, + &.kbq-focused { + &:has(.kbq-app-switcher-app__toggle) .kbq-app-switcher-app__icon { + visibility: hidden; + } + + & .kbq-app-switcher-app__toggle { + display: flex; + } + } + + &:hover, + &.kbq-hover { + background: var(--kbq-states-background-transparent-hover); + + & .kbq-app-switcher-app__toggle { + background: var(--kbq-states-background-transparent-hover); + } + } + + &:active, + &.kbq-active { + background: var(--kbq-states-background-transparent-active); + } + + &.kbq-selected { + background: var(--kbq-background-theme-less); + + &:hover, + &.kbq-hover { + background: var(--kbq-states-background-theme-less-hover); + } + + &:active, + &.kbq-active { + background: var(--kbq-states-background-theme-less-active); + } + } + + background: var(--kbq-list-default-container-background); + color: var(--kbq-list-default-text-color); + + &.cdk-keyboard-focused { + outline: none; + border-color: var(--kbq-list-states-focused-focus-outline-color); + } + } +} + +@mixin kbq-app-switcher-app-typography() { + .kbq-app-switcher-app__name { + @include tokens.kbq-typography-level-to-styles-css-variables(typography, text-normal-medium); + } + + .kbq-app-switcher-app__caption { + @include tokens.kbq-typography-level-to-styles-css-variables(typography, text-compact); + } +} + +@include kbq-app-switcher-app-theme(); +@include kbq-app-switcher-app-typography(); diff --git a/packages/components/app-switcher/app-switcher-app.ts b/packages/components/app-switcher/app-switcher-app.ts new file mode 100644 index 000000000..09f4c0293 --- /dev/null +++ b/packages/components/app-switcher/app-switcher-app.ts @@ -0,0 +1,60 @@ +import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; +import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; +import { KbqDropdownItem } from '@koobiq/components/dropdown'; +import { KbqIcon } from '@koobiq/components/icon'; + +@Component({ + standalone: true, + selector: '[kbq-app-switcher-app]', + exportAs: 'kbqAppSwitcherApp', + template: ` + + + + + + + + + +
+
{{ app.name }}
+ @if (app.caption) { +
{{ app.caption }}
+ } +
+ + @if (app.apps) { +
+ +
+ } + `, + styleUrls: ['app-switcher-app.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + host: { + class: 'kbq-app-switcher-app', + '[class.kbq-dropdown-item]': 'false' + }, + imports: [ + KbqIcon + ], + providers: [ + { provide: KBQ_TITLE_TEXT_REF, useExisting: KbqAppSwitcherApp }, + { provide: KbqDropdownItem, useExisting: KbqAppSwitcherApp } + ] +}) +export class KbqAppSwitcherApp extends KbqDropdownItem { + @Input('kbq-app-switcher-app') app; + + @Input() collapsed: boolean = false; + + clickHandler(event: MouseEvent) { + event.stopPropagation(); + event.preventDefault(); + + this.collapsed = !this.collapsed; + } +} diff --git a/packages/components/app-switcher/app-switcher-tree-option.html b/packages/components/app-switcher/app-switcher-tree-option.html deleted file mode 100644 index 5cb44de62..000000000 --- a/packages/components/app-switcher/app-switcher-tree-option.html +++ /dev/null @@ -1,27 +0,0 @@ - - -
- - - - - - -
- -
-
- kbq-app-switch-tree-option-text -
-
- kbq-app-switch-tree-option-alias -
-
diff --git a/packages/components/app-switcher/app-switcher-tree-option.scss b/packages/components/app-switcher/app-switcher-tree-option.scss deleted file mode 100644 index 5f66f5e10..000000000 --- a/packages/components/app-switcher/app-switcher-tree-option.scss +++ /dev/null @@ -1,159 +0,0 @@ -@use '../core/styles/common/tokens' as *; - -.kbq-app-switcher-tree-option { - position: relative; - - display: flex; - flex-direction: row; - - height: 38px; - - border-radius: var(--kbq-size-s); - - padding: var(--kbq-size-xxs) var(--kbq-size-s) var(--kbq-size-xs) var(--kbq-size-m); - - gap: var(--kbq-size-m); - - & .kbq-tree-node-toggle { - position: absolute; - - top: var(--kbq-size-m); - left: var(--kbq-size-m); - - width: var(--kbq-size-xxl); - height: var(--kbq-size-xxl); - - border-radius: var(--kbq-size-xxs); - - display: none; - - & .kbq-icon { - transform: rotate(0deg); - } - - &.kbq-expanded { - & .kbq-icon { - transform: rotate(180deg); - } - } - } - - &.kbq-app-switcher-tree-option_nested { - border-top-left-radius: unset; - border-bottom-left-radius: unset; - - &:after { - content: ''; - - position: absolute; - left: -1px; - top: 0; - - height: 100%; - - border-left-width: 1px; - border-left-style: solid; - } - - &:has(+ :not(.kbq-app-switcher-tree-option_nested)) { - border-bottom-left-radius: var(--kbq-size-s); - - &:after { - top: 0; - - width: var(--kbq-size-m); - height: var(--kbq-size-xxl); - - border-bottom-left-radius: var(--kbq-size-s); - border-bottom-width: 1px; - border-bottom-style: solid; - } - } - } -} - -.kbq-app-switch-option__container { - display: flex; - flex-direction: column; -} - -.kbq-app-switcher-tree-option__icon { - display: flex; - align-items: center; - - height: 38px; -} - - -@mixin kbq-app-switcher-tree-option-theme() { - .kbq-app-switcher-tree-option { - &:hover, - &.kbq-hover, - &.kbq-focused { - &:has(.kbq-tree-node-toggle) .kbq-app-switcher-tree-option__icon { - visibility: hidden; - } - - & .kbq-tree-node-toggle { - display: flex; - } - } - - &:hover, - &.kbq-hover { - background: var(--kbq-states-background-transparent-hover); - - & .kbq-tree-node-toggle { - background: var(--kbq-states-background-transparent-hover); - } - } - - &:active, - &.kbq-active { - background: var(--kbq-states-background-transparent-active); - } - - &.kbq-selected { - background: var(--kbq-background-theme-less); - - &:hover, - &.kbq-hover { - background: var(--kbq-states-background-theme-less-hover); - } - - &:active, - &.kbq-active { - background: var(--kbq-states-background-theme-less-active); - } - } - - &.kbq-focused { - outline: none; - } - } - - .kbq-app-switcher-tree-option_nested:after { - border-left-color: var(--kbq-line-contrast-less); - border-bottom-color: var(--kbq-line-contrast-less); - } - - .kbq-app-switch-tree-option__text { - } - - .kbq-app-switch-tree-option__alias { - color: var(--kbq-foreground-contrast-secondary); - } -} - -@mixin kbq-app-switcher-tree-option-typography() { - .kbq-app-switch-tree-option__text { - @include kbq-typography-level-to-styles-css-variables(typography, text-normal-medium); - } - - .kbq-app-switch-tree-option__alias { - @include kbq-typography-level-to-styles-css-variables(typography, text-compact); - } -} - -@include kbq-app-switcher-tree-option-theme(); -@include kbq-app-switcher-tree-option-typography(); diff --git a/packages/components/app-switcher/app-switcher-tree-option.ts b/packages/components/app-switcher/app-switcher-tree-option.ts deleted file mode 100644 index 9ad7be6f4..000000000 --- a/packages/components/app-switcher/app-switcher-tree-option.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core'; -import { KbqTreeNode, KbqTreeOption } from '@koobiq/components/tree'; - -import { Directionality } from '@angular/cdk/bidi'; -import { AfterViewInit, Directive, ElementRef, Renderer2 } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { KbqTreeBase } from '@koobiq/components/tree'; - -@Component({ - standalone: true, - selector: 'kbq-app-switcher-tree-option', - exportAs: 'kbqAppSwitcherTreeOption', - templateUrl: './app-switcher-tree-option.html', - styleUrls: ['./app-switcher-tree-option.scss'], - host: { - class: 'kbq-app-switcher-tree-option', - '[class.kbq-tree-option]': 'null' - }, - changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - providers: [ - { provide: KbqTreeNode, useExisting: KbqAppSwitcherTreeOption }, - { provide: KbqTreeOption, useExisting: KbqAppSwitcherTreeOption } - ] -}) -export class KbqAppSwitcherTreeOption extends KbqTreeOption {} - -@Directive({ - standalone: true, - selector: '[kbqAppSwitcherTreeNodePadding]', - exportAs: 'kbqAppSwitcherTreeNodePadding' -}) -export class KbqAppSwitcherTreeNodePadding implements AfterViewInit { - renderer = inject(Renderer2); - nativeElement = inject(ElementRef).nativeElement; - - tree = inject(KbqTreeBase); - treeNode = inject(KbqTreeNode); - dir = inject(Directionality, { optional: true }); - - private indent: number = 24; - - get leftPaddingForFirstLevel(): number { - return 0; - } - - /** CSS units used for the indentation value. */ - indentUnits = 'px'; - - constructor() { - this.dir?.change?.pipe(takeUntilDestroyed()).subscribe(() => this.setPadding()); - } - - ngAfterViewInit(): void { - this.setPadding(); - this.addLevelClass(); - } - - paddingIndent(): string | null { - const nodeLevel = - this.treeNode.data && this.tree.treeControl.getLevel - ? this.tree.treeControl.getLevel(this.treeNode.data) - : 0; - - return nodeLevel > 0 - ? `${nodeLevel * this.indent}${this.indentUnits}` - : `${this.leftPaddingForFirstLevel}${this.indentUnits}`; - } - - private setPadding() { - const padding = this.paddingIndent(); - const paddingProp = this.dir?.value === 'rtl' ? 'marginRight' : 'marginLeft'; - - this.renderer.setStyle(this.nativeElement, paddingProp, padding); - } - - private addLevelClass() { - const nodeLevel = - this.treeNode.data && this.tree.treeControl.getLevel - ? this.tree.treeControl.getLevel(this.treeNode.data) - : 0; - - const className = nodeLevel > 0 ? 'kbq-app-switcher-tree-option_nested' : 'kbq-app-switcher-tree-option_root'; - - this.renderer.addClass(this.nativeElement, className); - } -} diff --git a/packages/components/app-switcher/app-switcher-tree.scss b/packages/components/app-switcher/app-switcher-tree.scss deleted file mode 100644 index f2a5702ad..000000000 --- a/packages/components/app-switcher/app-switcher-tree.scss +++ /dev/null @@ -1,4 +0,0 @@ -.kbq-app-switcher-tree { - display: flex; - flex-direction: column; -} diff --git a/packages/components/app-switcher/app-switcher-tree.ts b/packages/components/app-switcher/app-switcher-tree.ts deleted file mode 100644 index dc3b6187e..000000000 --- a/packages/components/app-switcher/app-switcher-tree.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - forwardRef, - ViewEncapsulation -} from '@angular/core'; -import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { KbqHighlightModule } from '@koobiq/components/core'; -import { - KBQ_TREE_OPTION_PARENT_COMPONENT, - KbqTreeBase, - KbqTreeModule, - KbqTreeSelection -} from '@koobiq/components/tree'; - -@Component({ - standalone: true, - selector: 'kbq-app-switcher-tree', - template: '', - preserveWhitespaces: false, - styleUrls: ['./app-switcher-tree.scss'], - host: { - class: 'kbq-app-switcher-tree', - '[class.kbq-tree-selection]': 'null' - }, - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - KbqHighlightModule, - KbqTreeModule, - FormsModule - ], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => KbqAppSwitcherTree), - multi: true - }, - { provide: KBQ_TREE_OPTION_PARENT_COMPONENT, useExisting: KbqAppSwitcherTree }, - { provide: KbqTreeBase, useExisting: KbqAppSwitcherTree } - ] -}) -export class KbqAppSwitcherTree extends KbqTreeSelection {} diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index 729fdf04a..fb134e6a2 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -10,23 +10,25 @@ } -
+
Название площадки
Главная
- - - - + +
+ @for (app of sites[0].apps; track app) { + - - - - - - + @if (!appSwitcherApp.collapsed) { + @for (app of app.apps; track app) { + + } + } + } +
+
@if (withSites) { diff --git a/packages/components/app-switcher/app-switcher.module.ts b/packages/components/app-switcher/app-switcher.module.ts index 1d038d010..b219bdf49 100644 --- a/packages/components/app-switcher/app-switcher.module.ts +++ b/packages/components/app-switcher/app-switcher.module.ts @@ -19,16 +19,17 @@ import { KbqAppSwitcher, KbqAppSwitcherTrigger } from './app-switcher'; -import { KbqAppSwitcherTree } from './app-switcher-tree'; -import { KbqAppSwitcherTreeNodePadding, KbqAppSwitcherTreeOption } from './app-switcher-tree-option'; +import { KbqAppSwitcherApp } from './app-switcher-app'; +import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; +import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; @NgModule({ imports: [ KbqAppSwitcher, KbqAppSwitcherTrigger, - KbqAppSwitcherTree, - KbqAppSwitcherTreeOption, - KbqAppSwitcherTreeNodePadding, + KbqAppSwitcherApp, + KbqAppSwitcherDropdownApp, + KbqAppSwitcherDropdownSite, OverlayModule, KbqButtonModule, A11yModule, @@ -42,10 +43,7 @@ import { KbqAppSwitcherTreeNodePadding, KbqAppSwitcherTreeOption } from './app-s ], exports: [ KbqAppSwitcher, - KbqAppSwitcherTrigger, - KbqAppSwitcherTree, - KbqAppSwitcherTreeOption, - KbqAppSwitcherTreeNodePadding + KbqAppSwitcherTrigger ], providers: [ KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER, diff --git a/packages/components/app-switcher/app-switcher.scss b/packages/components/app-switcher/app-switcher.scss index 2561527ba..22402584c 100644 --- a/packages/components/app-switcher/app-switcher.scss +++ b/packages/components/app-switcher/app-switcher.scss @@ -3,6 +3,9 @@ .kbq-app-switcher { position: relative; + display: flex; + flex-direction: column; + margin-top: var(--kbq-size-xxs); margin-bottom: var(--kbq-size-xxs); @@ -21,12 +24,20 @@ padding-top: var(--kbq-size-xxs); } -.kbq-app-switcher-group-header { - padding: var(--kbq-size-s) var(--kbq-size-l); - +.kbq-app-switcher__app-container { display: flex; - flex-direction: row; - justify-content: space-between; + flex-direction: column; + min-height: 0; + + padding: var(--kbq-size-s) var(--kbq-size-xxs); + + & .kbq-app-switcher-group-header { + padding: var(--kbq-size-s) var(--kbq-size-l); + + display: flex; + flex-direction: row; + justify-content: space-between; + } } .kbq-app-switcher__sites-container { diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index 4f0237dde..7688cd0f0 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -45,13 +45,13 @@ import { KbqFormFieldModule } from '@koobiq/components/form-field'; import { KbqIcon } from '@koobiq/components/icon'; import { KbqInputModule } from '@koobiq/components/input'; import { defaultOffsetYWithArrow } from '@koobiq/components/popover'; +import { KbqScrollbarModule } from '@koobiq/components/scrollbar'; import { FlatTreeControl, KbqTreeFlatDataSource, KbqTreeFlattener, KbqTreeModule } from '@koobiq/components/tree'; -import { merge } from 'rxjs'; +import { merge, Subscription } from 'rxjs'; import { kbqAppSwitcherAnimations } from './app-switcher-animations'; +import { KbqAppSwitcherApp } from './app-switcher-app'; import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; -import { KbqAppSwitcherTree } from './app-switcher-tree'; -import { KbqAppSwitcherTreeNodePadding, KbqAppSwitcherTreeOption } from './app-switcher-tree-option'; export class FileNode { children: FileNode[]; @@ -140,15 +140,14 @@ export const DATA_OBJECT = { KbqInputModule, FormsModule, KbqDividerModule, - KbqAppSwitcherTree, KbqBadgeModule, KbqIcon, KbqTreeModule, - KbqAppSwitcherTreeOption, - KbqAppSwitcherTreeNodePadding, KbqDropdownModule, KbqAppSwitcherDropdownApp, - KbqAppSwitcherDropdownSite + KbqAppSwitcherDropdownSite, + KbqAppSwitcherApp, + KbqScrollbarModule ], animations: [kbqAppSwitcherAnimations.state] }) @@ -641,6 +640,8 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple }; } + protected preventClosingByInnerScrollSubscription: Subscription; + ngOnInit(): void { super.ngOnInit(); @@ -663,6 +664,19 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple } }); } + + this.visibleChange.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((visible: boolean) => { + if (visible) { + this.preventClosingByInnerScrollSubscription = this.closingActions().subscribe((event) => { + if (event['scrollDispatcher']) { + event['kbqPopoverPreventHide'] = true; + event['type'] = 'click'; + } + }); + } else { + this.preventClosingByInnerScrollSubscription.unsubscribe(); + } + }); } updateData() { diff --git a/packages/components/app-switcher/public-api.ts b/packages/components/app-switcher/public-api.ts index d3d5a6232..81c99bf33 100644 --- a/packages/components/app-switcher/public-api.ts +++ b/packages/components/app-switcher/public-api.ts @@ -1,7 +1,6 @@ export * from './app-switcher'; export * from './app-switcher-animations'; +export * from './app-switcher-app'; export * from './app-switcher-dropdown-app'; export * from './app-switcher-dropdown-site'; -export * from './app-switcher-tree'; -export * from './app-switcher-tree-option'; export * from './app-switcher.module'; From 3be1767e297991b33fa6c24f8429ce84d167a328 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Fri, 19 Sep 2025 16:14:00 +0300 Subject: [PATCH 10/17] feat: search states --- .../components-dev/app-switcher/module.ts | 15 ++- .../components-dev/app-switcher/styles.scss | 9 ++ .../components-dev/app-switcher/template.html | 14 ++ .../app-switcher/app-switcher-app.scss | 5 - .../app-switcher/app-switcher-app.ts | 15 ++- .../components/app-switcher/app-switcher.html | 122 +++++++++++------- .../app-switcher/app-switcher.spec.ts | 4 +- .../components/app-switcher/app-switcher.ts | 7 +- .../app-switcher/examples.app-switcher.en.md | 1 - 9 files changed, 124 insertions(+), 68 deletions(-) diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index 99bc19c6c..e49e6afcd 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -6,14 +6,16 @@ import { DATA_OBJECT, FileFlatNode, FileNode, + KbqAppSwitcherApp, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; import { KbqBadgeModule } from '@koobiq/components/badge'; import { KbqButtonModule } from '@koobiq/components/button'; -import { KbqComponentColors } from '@koobiq/components/core'; +import { KbqComponentColors, KbqOptionModule } from '@koobiq/components/core'; import { KbqDividerModule } from '@koobiq/components/divider'; import { KbqDropdownModule } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; +import { KbqScrollbar } from '@koobiq/components/scrollbar'; import { FlatTreeControl, KbqTreeFlatDataSource, KbqTreeFlattener, KbqTreeModule } from '@koobiq/components/tree'; import { PopoverExamplesModule } from 'packages/docs-examples/components/popover'; @@ -43,7 +45,10 @@ class DevExamples {} KbqTreeModule, KbqDropdownModule, KbqDividerModule, - KbqBadgeModule + KbqBadgeModule, + KbqScrollbar, + KbqAppSwitcherApp, + KbqOptionModule ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush @@ -94,7 +99,8 @@ export class DevApp { }, { name: 'Name', - caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', apps: [ { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' @@ -113,7 +119,8 @@ export class DevApp { }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', apps: [ { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' diff --git a/packages/components-dev/app-switcher/styles.scss b/packages/components-dev/app-switcher/styles.scss index e69de29bb..ef3032c61 100644 --- a/packages/components-dev/app-switcher/styles.scss +++ b/packages/components-dev/app-switcher/styles.scss @@ -0,0 +1,9 @@ +.kbq-app-switcher__search-result { + max-width: 320px; + + display: flex; + flex-direction: column; + min-height: 0; + + padding: var(--kbq-size-xxs) var(--kbq-size-xxs) var(--kbq-size-s) var(--kbq-size-xxs); +} diff --git a/packages/components-dev/app-switcher/template.html b/packages/components-dev/app-switcher/template.html index 3953e193e..76549431a 100644 --- a/packages/components-dev/app-switcher/template.html +++ b/packages/components-dev/app-switcher/template.html @@ -4,6 +4,20 @@

+
+ +
+ @for (site of sites; track site) { + + + @for (app of site.apps; track app) { + + } + } +
+
+
+


diff --git a/packages/components/app-switcher/app-switcher-app.scss b/packages/components/app-switcher/app-switcher-app.scss index da11d0042..9022225a1 100644 --- a/packages/components/app-switcher/app-switcher-app.scss +++ b/packages/components/app-switcher/app-switcher-app.scss @@ -74,7 +74,6 @@ border-radius: var(--kbq-size-xxs); - & .kbq-icon { transform: rotate(0deg); } @@ -109,15 +108,11 @@ } @mixin kbq-app-switcher-app-theme() { - .kbq-app-switcher-site_nested:after { border-left-color: var(--kbq-line-contrast-less); border-bottom-color: var(--kbq-line-contrast-less); } - .kbq-app-switcher-app__name { - } - .kbq-app-switcher-app__caption { color: var(--kbq-foreground-contrast-secondary); } diff --git a/packages/components/app-switcher/app-switcher-app.ts b/packages/components/app-switcher/app-switcher-app.ts index 09f4c0293..be2ef1a95 100644 --- a/packages/components/app-switcher/app-switcher-app.ts +++ b/packages/components/app-switcher/app-switcher-app.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; +import { booleanAttribute, ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core'; import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; @@ -14,8 +14,12 @@ import { KbqIcon } from '@koobiq/components/icon'; - - + +
@@ -25,7 +29,7 @@ import { KbqIcon } from '@koobiq/components/icon'; }
- @if (app.apps) { + @if (toggle) {
@@ -47,7 +51,8 @@ import { KbqIcon } from '@koobiq/components/icon'; ] }) export class KbqAppSwitcherApp extends KbqDropdownItem { - @Input('kbq-app-switcher-app') app; + @Input() app; + @Input({ transform: booleanAttribute }) toggle = false; @Input() collapsed: boolean = false; diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index fb134e6a2..020edf548 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -10,72 +10,94 @@ } -
-
-
Название площадки
- Главная -
+@if (!searchValue) { +
+
+
Название площадки
+ Главная +
- -
- @for (app of sites[0].apps; track app) { - + +
+ @for (app of sites[0].apps; track app) { + - @if (!appSwitcherApp.collapsed) { - @for (app of app.apps; track app) { - + @if (!appSwitcherApp.collapsed) { + @for (alias of app.apps; track alias) { + + } } } - } -
-
-
+
+ +
-@if (withSites) { - + @if (withSites) { + -
-
-
Другие площадки
+
+
+
Другие площадки
+
+ + +
+ @for (site of sites; track site) { + + } +
+
- -
- @for (site of sites; track site) { + + @for (app of activeSite?.apps; track app) { + @if (app.apps) { + } @else { + } -
+ }
-
- - @for (app of activeSite?.apps; track app) { - @if (app.apps) { - - } @else { + + @for (app of activeApp?.apps; track app) { } - } - + + } +} @else { +
+ +
+ @for (site of sites; track site) { + - - @for (app of activeApp?.apps; track app) { - - } - + @for (app of site.apps; track app) { + + } + } +
+
+
} diff --git a/packages/components/app-switcher/app-switcher.spec.ts b/packages/components/app-switcher/app-switcher.spec.ts index d19f6f111..7cad1e491 100644 --- a/packages/components/app-switcher/app-switcher.spec.ts +++ b/packages/components/app-switcher/app-switcher.spec.ts @@ -6,7 +6,6 @@ import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler'; import { TestScheduler } from 'rxjs/testing'; import { KbqAppSwitcherModule } from './app-switcher.module'; - describe('KbqAppSwitcher', () => { let fixture: ComponentFixture; let componentInstance: AppSwitcherSimple; @@ -48,6 +47,9 @@ describe('KbqAppSwitcher', () => { }); it('kbqTrigger = hover', fakeAsync(() => { + console.log('componentInstance: ', componentInstance); + console.log('debugElement: ', debugElement); + console.log('overlayContainerElement: ', overlayContainerElement); expect(true).toBe(true); })); }); diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index 7688cd0f0..c32055637 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -32,6 +32,7 @@ import { FormsModule } from '@angular/forms'; import { KbqBadgeModule } from '@koobiq/components/badge'; import { KbqComponentColors, + KbqOptionModule, KbqPopUp, KbqPopUpTrigger, POSITION_TO_CSS_MAP, @@ -47,7 +48,7 @@ import { KbqInputModule } from '@koobiq/components/input'; import { defaultOffsetYWithArrow } from '@koobiq/components/popover'; import { KbqScrollbarModule } from '@koobiq/components/scrollbar'; import { FlatTreeControl, KbqTreeFlatDataSource, KbqTreeFlattener, KbqTreeModule } from '@koobiq/components/tree'; -import { merge, Subscription } from 'rxjs'; +import { Subscription, merge } from 'rxjs'; import { kbqAppSwitcherAnimations } from './app-switcher-animations'; import { KbqAppSwitcherApp } from './app-switcher-app'; import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; @@ -147,7 +148,8 @@ export const DATA_OBJECT = { KbqAppSwitcherDropdownApp, KbqAppSwitcherDropdownSite, KbqAppSwitcherApp, - KbqScrollbarModule + KbqScrollbarModule, + KbqOptionModule ], animations: [kbqAppSwitcherAnimations.state] }) @@ -667,6 +669,7 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple this.visibleChange.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((visible: boolean) => { if (visible) { + // eslint-disable-next-line rxjs/no-nested-subscribe this.preventClosingByInnerScrollSubscription = this.closingActions().subscribe((event) => { if (event['scrollDispatcher']) { event['kbqPopoverPreventHide'] = true; diff --git a/packages/components/app-switcher/examples.app-switcher.en.md b/packages/components/app-switcher/examples.app-switcher.en.md index 8b1378917..e69de29bb 100644 --- a/packages/components/app-switcher/examples.app-switcher.en.md +++ b/packages/components/app-switcher/examples.app-switcher.en.md @@ -1 +0,0 @@ - From 2606f8353f7571941d34719fa5f00d1e720adf36 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Fri, 26 Sep 2025 15:55:45 +0300 Subject: [PATCH 11/17] fix: styles and params --- .../components-dev/app-switcher/module.ts | 122 ++--- .../components-dev/app-switcher/template.html | 30 +- .../app-switcher/app-switcher-app.scss | 2 +- .../app-switcher-dropdown-site.scss | 2 + .../components/app-switcher/app-switcher.html | 157 +++--- .../components/app-switcher/app-switcher.scss | 12 +- .../components/app-switcher/app-switcher.ts | 456 +----------------- 7 files changed, 153 insertions(+), 628 deletions(-) diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index e49e6afcd..1cd3e7775 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -1,22 +1,13 @@ import { A11yModule } from '@angular/cdk/a11y'; import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { - buildFileTree, - DATA_OBJECT, - FileFlatNode, - FileNode, - KbqAppSwitcherApp, - KbqAppSwitcherModule -} from '@koobiq/components/app-switcher'; +import { KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; import { KbqBadgeModule } from '@koobiq/components/badge'; import { KbqButtonModule } from '@koobiq/components/button'; import { KbqComponentColors, KbqOptionModule } from '@koobiq/components/core'; import { KbqDividerModule } from '@koobiq/components/divider'; import { KbqDropdownModule } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; -import { KbqScrollbar } from '@koobiq/components/scrollbar'; -import { FlatTreeControl, KbqTreeFlatDataSource, KbqTreeFlattener, KbqTreeModule } from '@koobiq/components/tree'; import { PopoverExamplesModule } from 'packages/docs-examples/components/popover'; @Component({ @@ -24,44 +15,13 @@ import { PopoverExamplesModule } from 'packages/docs-examples/components/popover imports: [PopoverExamplesModule, KbqButtonModule, KbqIcon, KbqAppSwitcherModule], selector: 'dev-examples', template: ` - `, changeDetection: ChangeDetectionStrategy.OnPush }) -class DevExamples {} - -@Component({ - standalone: true, - selector: 'dev-app', - styleUrls: ['./styles.scss'], - templateUrl: './template.html', - imports: [ - A11yModule, - FormsModule, - DevExamples, - KbqAppSwitcherModule, - KbqTreeModule, - KbqDropdownModule, - KbqDividerModule, - KbqBadgeModule, - KbqScrollbar, - KbqAppSwitcherApp, - KbqOptionModule - ], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DevApp { - componentColors = KbqComponentColors; - - treeControl: FlatTreeControl; - treeFlattener: KbqTreeFlattener; - dataSource: KbqTreeFlatDataSource; - - modelValue: any = ''; - +class DevExamples { sites = [ { name: 'ЦФО', @@ -364,59 +324,31 @@ export class DevApp { ] } ]; +} - activeSite; - activeApp; - - constructor() { - this.treeFlattener = new KbqTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); - - this.treeControl = new FlatTreeControl( - this.getLevel, - this.isExpandable, - this.getValue, - this.getViewValue - ); - this.dataSource = new KbqTreeFlatDataSource(this.treeControl, this.treeFlattener); - - this.dataSource.data = buildFileTree(DATA_OBJECT, 0); - } - - hasChild(_: number, nodeData: FileFlatNode) { - return nodeData.expandable; - } - - private transformer = (node: FileNode, level: number, parent: any) => { - const flatNode = new FileFlatNode(); - - flatNode.name = node.name; - flatNode.parent = parent; - flatNode.type = node.type; - flatNode.level = level; - flatNode.expandable = !!node.children; - - return flatNode; - }; - - private getLevel = (node: FileFlatNode) => { - return node.level; - }; - - private isExpandable = (node: FileFlatNode) => { - return node.expandable; - }; - - private getChildren = (node: FileNode): FileNode[] => { - return node.children; - }; - - private getValue = (node: FileFlatNode): string => { - return node.name; - }; +@Component({ + standalone: true, + selector: 'dev-app', + styleUrls: ['./styles.scss'], + templateUrl: './template.html', + imports: [ + A11yModule, + FormsModule, + DevExamples, + KbqAppSwitcherModule, + KbqDropdownModule, + KbqDividerModule, + KbqBadgeModule, + KbqOptionModule + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DevApp { + componentColors = KbqComponentColors; - private getViewValue = (node: FileFlatNode): string => { - const nodeType = node.type ? `.${node.type}` : ''; + modelValue: any = ''; - return `${node.name}${nodeType}`; - }; + activeSite; + activeApp; } diff --git a/packages/components-dev/app-switcher/template.html b/packages/components-dev/app-switcher/template.html index 76549431a..248ebbf6e 100644 --- a/packages/components-dev/app-switcher/template.html +++ b/packages/components-dev/app-switcher/template.html @@ -4,20 +4,20 @@

-
- -
- @for (site of sites; track site) { - + + + + + - @for (app of site.apps; track app) { - - } - } -
-
-
+ + + + + + + -
-
-
+ + + diff --git a/packages/components/app-switcher/app-switcher-app.scss b/packages/components/app-switcher/app-switcher-app.scss index 9022225a1..41f3b1f82 100644 --- a/packages/components/app-switcher/app-switcher-app.scss +++ b/packages/components/app-switcher/app-switcher-app.scss @@ -12,7 +12,7 @@ border-radius: var(--kbq-size-s); - padding: var(--kbq-size-xxs) var(--kbq-size-s) var(--kbq-size-xs) var(--kbq-size-m); + padding: var(--kbq-size-xxs) var(--kbq-size-m) var(--kbq-size-xs) var(--kbq-size-m); @include common.user-select(none); diff --git a/packages/components/app-switcher/app-switcher-dropdown-site.scss b/packages/components/app-switcher/app-switcher-dropdown-site.scss index 5004063e4..7dbbeab18 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-site.scss +++ b/packages/components/app-switcher/app-switcher-dropdown-site.scss @@ -17,6 +17,8 @@ @include common.user-select(none); @include common.kbq-list-item-base(); + padding: var(--kbq-size-xxs) var(--kbq-size-m); + &:not(.kbq-disabled) { cursor: pointer; } diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index 020edf548..45c90ed1e 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -1,8 +1,15 @@ -@if (withSearch) { +@if (trigger.search) {
- +
@@ -10,94 +17,96 @@ } -@if (!searchValue) { -
-
-
Название площадки
- Главная -
+
+ @if (!searchValue) { +
+
+
Название площадки
+ Главная +
- -
- @for (app of sites[0].apps; track app) { - + +
+ @for (app of trigger.sites[0].apps; track app) { + - @if (!appSwitcherApp.collapsed) { - @for (alias of app.apps; track alias) { - + @if (!appSwitcherApp.collapsed) { + @for (alias of app.apps; track alias) { + + } } } - } -
-
-
+
+ +
- @if (withSites) { - + @if (withSites) { + -
-
-
Другие площадки
+
+
+
Другие площадки
+
+ + +
+ @for (site of trigger.sites; track site) { + + } +
+
- -
- @for (site of sites; track site) { + + @for (app of activeSite?.apps; track app) { + @if (app.apps) { + } @else { + } -
+ }
-
- - @for (app of activeSite?.apps; track app) { - @if (app.apps) { - - } @else { + + @for (app of activeApp?.apps; track app) { } - } - - - - @for (app of activeApp?.apps; track app) { - - } - - } -} @else { -
- -
- @for (site of sites; track site) { - + + } + } @else { +
+ +
+ @for (site of trigger.sites; track site) { + - @for (app of site.apps; track app) { - + @for (app of site.apps; track app) { + + } } - } -
-
-
-} +
+
+
+ } +
diff --git a/packages/components/app-switcher/app-switcher.scss b/packages/components/app-switcher/app-switcher.scss index 22402584c..89d542010 100644 --- a/packages/components/app-switcher/app-switcher.scss +++ b/packages/components/app-switcher/app-switcher.scss @@ -30,14 +30,14 @@ min-height: 0; padding: var(--kbq-size-s) var(--kbq-size-xxs); +} - & .kbq-app-switcher-group-header { - padding: var(--kbq-size-s) var(--kbq-size-l); +.kbq-app-switcher-group-header { + padding: var(--kbq-size-s) var(--kbq-size-l); - display: flex; - flex-direction: row; - justify-content: space-between; - } + display: flex; + flex-direction: row; + justify-content: space-between; } .kbq-app-switcher__sites-container { diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index c32055637..12ef78d0b 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -44,87 +44,15 @@ import { KbqDividerModule } from '@koobiq/components/divider'; import { KbqDropdownModule, KbqDropdownTrigger } from '@koobiq/components/dropdown'; import { KbqFormFieldModule } from '@koobiq/components/form-field'; import { KbqIcon } from '@koobiq/components/icon'; -import { KbqInputModule } from '@koobiq/components/input'; +import { KbqInput, KbqInputModule } from '@koobiq/components/input'; import { defaultOffsetYWithArrow } from '@koobiq/components/popover'; import { KbqScrollbarModule } from '@koobiq/components/scrollbar'; -import { FlatTreeControl, KbqTreeFlatDataSource, KbqTreeFlattener, KbqTreeModule } from '@koobiq/components/tree'; import { Subscription, merge } from 'rxjs'; import { kbqAppSwitcherAnimations } from './app-switcher-animations'; import { KbqAppSwitcherApp } from './app-switcher-app'; import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; -export class FileNode { - children: FileNode[]; - name: string; - type: any; -} - -/** Flat node with expandable and level information */ -export class FileFlatNode { - name: string; - type: any; - level: number; - expandable: boolean; - parent: any; -} - -export function buildFileTree(value: any, level: number): FileNode[] { - const data: any[] = []; - - for (const k of Object.keys(value)) { - const v = value[k]; - const node = new FileNode(); - - node.name = `${k}`; - - if (v === null || v === undefined) { - // no action - } else if (typeof v === 'object') { - node.children = buildFileTree(v, level + 1); - } else { - node.type = v; - } - - data.push(node); - } - - return data; -} - -export const DATA_OBJECT = { - docs: 'app', - src: { - 'aria-describer': 'ts', - 'aria-describer.spec': 'ts', - 'aria-reference': 'ts', - 'aria-reference.spec': 'ts' - }, - documentation: { - source: '', - tools: '' - }, - mosaic: { - autocomplete: '', - button: '', - 'button-toggle': '', - index: 'ts', - package: 'json', - version: 'ts' - }, - 'components-dev': { - alert: '', - badge: '' - }, - scripts: { - 'cleanup-preview': 'ts', - 'publish-artifacts': 'sh', - 'publish-docs': 'sh', - 'publish-docs-preview': 'ts' - }, - tests: '' -}; - @Component({ standalone: true, selector: 'kbq-app-switcher', @@ -143,7 +71,6 @@ export const DATA_OBJECT = { KbqDividerModule, KbqBadgeModule, KbqIcon, - KbqTreeModule, KbqDropdownModule, KbqAppSwitcherDropdownApp, KbqAppSwitcherDropdownSite, @@ -157,7 +84,6 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { protected readonly componentColors = KbqComponentColors; searchValue: string = ''; - withSearch: boolean = true; withSites: boolean = true; prefix = 'kbq-app-switcher'; @@ -169,33 +95,21 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { activeSite; activeApp; + @ViewChild(KbqInput) input: KbqInput; @ViewChild('appSwitcherContent') appSwitcherContent: ElementRef; @ViewChild('appSwitcher') elementRef: ElementRef; @ViewChild('otherSites') otherSites: KbqDropdownTrigger; @ViewChild(CdkTrapFocus) cdkTrapFocus: CdkTrapFocus; - constructor() { - super(); - - this.treeFlattener = new KbqTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); - - this.treeControl = new FlatTreeControl( - this.getLevel, - this.isExpandable, - this.getValue, - this.getViewValue - ); - this.dataSource = new KbqTreeFlatDataSource(this.treeControl, this.treeFlattener); - - this.dataSource.data = buildFileTree(DATA_OBJECT, 0); - } - ngAfterViewInit() { - if (!this.appSwitcherContent) { - return; + if (this.input) { + this.input.focus(); } + if (!this.appSwitcherContent) return; + this.cdkTrapFocus.focusTrap.focusFirstTabbableElement(); + this.visibleChange.subscribe((state) => { if (this.offset !== null && state) { applyPopupMargins( @@ -216,353 +130,16 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { this.isTrapFocus = isTrapFocus; } - treeControl: FlatTreeControl; - treeFlattener: KbqTreeFlattener; - - dataSource: KbqTreeFlatDataSource; - - modelValue: any = ''; - - sites = [ - { - name: 'ЦФО', - apps: [ - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - apps: [ - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - } - ] - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - apps: [ - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - } - ] - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - apps: [ - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - } - ] - } - ] - }, - { - name: 'СЗФО', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001', - icon: '' - }, - { - name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' - }, - { - name: 'SentraLock', - caption: 'Lock-sentral-urals' - }, - { - name: 'Zero Trace' - } - ] - }, - { - name: 'ЮФО', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001' - }, - { - name: 'CryptoWall' - }, - { - name: 'Phantom Gate' - }, - { - name: 'SentraLock', - caption: 'Lock-sentral-urals' - }, - { - name: 'Zero Trace' - } - ] - }, - { - name: 'Южный Суверенный Федеральный Округ ФО', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001' - }, - { - name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' - }, - { - name: 'SentraLock', - caption: 'Lock-sentral-urals' - }, - { - name: 'Zero Trace' - } - ], - main: true - }, - { - name: 'ПФО', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001' - }, - { - name: 'CryptoWall' - }, - { - name: 'Phantom Gate' - }, - { - name: 'SentraLock', - caption: 'Lock-sentral-urals' - }, - { - name: 'Zero Trace' - } - ] - }, - { - name: 'УФО', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001' - }, - { - name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' - }, - { - name: 'SentraLock', - caption: 'Lock-sentral-urals' - }, - { - name: 'Zero Trace' - } - ] - }, - { - name: 'СФО', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001' - }, - { - name: 'CryptoWall' - }, - { - name: 'Phantom Gate' - }, - { - name: 'SentraLock', - caption: 'Lock-sentral-urals' - }, - { - name: 'Zero Trace' - } - ] - }, - { - name: 'ДФО', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001' - }, - { - name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' - }, - { - name: 'SentraLock', - caption: 'Lock-sentral-urals' - }, - { - name: 'Zero Trace' - } - ] - } - ]; - - hasChild(_: number, nodeData: FileFlatNode) { - return nodeData.expandable; + onSearchChange(value: string) { + this.searchValue = value; + console.log('onSearchChange: ', value); } - private transformer = (node: FileNode, level: number, parent: any) => { - const flatNode = new FileFlatNode(); - - flatNode.name = node.name; - flatNode.parent = parent; - flatNode.type = node.type; - flatNode.level = level; - flatNode.expandable = !!node.children; - - return flatNode; - }; - - private getLevel = (node: FileFlatNode) => { - return node.level; - }; - - private isExpandable = (node: FileFlatNode) => { - return node.expandable; - }; - - private getChildren = (node: FileNode): FileNode[] => { - return node.children; - }; - - private getValue = (node: FileFlatNode): string => { - return node.name; - }; - - private getViewValue = (node: FileFlatNode): string => { - const nodeType = node.type ? `.${node.type}` : ''; + onEscape() { + this.hide(0); + } - return `${node.name}${nodeType}`; - }; + modelValue: any = ''; } export const KBQ_APP_SWITCHER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( @@ -605,6 +182,10 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple footer: string | TemplateRef; private closeOnScroll: null; + @Input({ transform: booleanAttribute }) search: boolean; + + @Input() sites; + @Input({ transform: booleanAttribute }) get disabled(): boolean { return this._disabled; @@ -678,6 +259,7 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple }); } else { this.preventClosingByInnerScrollSubscription.unsubscribe(); + this.focus(); } }); } From 4357f387c601c954310f8e98ff664100f987e2bb Mon Sep 17 00:00:00 2001 From: lskramarov Date: Mon, 29 Sep 2025 21:33:42 +0300 Subject: [PATCH 12/17] feat: structure + grouping --- .../components-dev/app-switcher/module.ts | 453 +++++++++++------- .../app-switcher-dropdown-site.scss | 4 - .../app-switcher-dropdown-site.ts | 4 +- .../components/app-switcher/app-switcher.html | 37 +- .../components/app-switcher/app-switcher.ts | 86 +++- 5 files changed, 393 insertions(+), 191 deletions(-) diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index 1cd3e7775..6a2e0d13c 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -1,7 +1,7 @@ import { A11yModule } from '@angular/cdk/a11y'; import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; +import { KbaAppSwitcherSite, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; import { KbqBadgeModule } from '@koobiq/components/badge'; import { KbqButtonModule } from '@koobiq/components/button'; import { KbqComponentColors, KbqOptionModule } from '@koobiq/components/core'; @@ -15,315 +15,445 @@ import { PopoverExamplesModule } from 'packages/docs-examples/components/popover imports: [PopoverExamplesModule, KbqButtonModule, KbqIcon, KbqAppSwitcherModule], selector: 'dev-examples', template: ` - `, changeDetection: ChangeDetectionStrategy.OnPush }) class DevExamples { - sites = [ + sites: KbaAppSwitcherSite[] = [ { name: 'ЦФО', + id: '01', apps: [ { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + id: 'CFO_01', + type: 'type_1' + }, + { + name: 'Name', + id: 'CFO_02', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + type: 'type_1' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + id: 'CFO_03', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + type: 'type_1' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + id: 'CFO_04', + type: 'type_1' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + id: 'CFO_05', + type: 'type_2' }, { name: 'Name', + id: 'CFO_06', caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + type: 'type_2' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + id: 'CFO_07', caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + type: 'type_2' + }, + { + name: 'Name', + id: 'CFO_08', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + type: 'type_2' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - apps: [ - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - } - ] + id: 'CFO_09', + type: 'type_3' }, { name: 'Name', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - apps: [ - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - } - ] + id: 'CFO_10', + type: 'type_3' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - apps: [ - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name' - }, - { - name: 'Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - }, - { - name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - caption: - 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption' - } - ] + id: 'CFO_11', + type: 'type_3' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + id: 'CFO_12', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + type: 'type_3' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + id: 'CFO_13', + type: 'type_4' + }, + { + name: 'Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + id: 'CFO_14', + type: 'type_4' + }, + { + name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', + caption: + 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', + id: 'CFO_15', + type: 'type_4' } ] }, { name: 'СЗФО', + id: '02', apps: [ { name: 'Byte Sentinel', caption: 'Byte 001', - icon: '' + id: 'SZFO_01', + icon: '', + type: '' }, { name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' + id: 'SZFO_02', + type: '' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'SZFO_03', + type: '' + }, + { + name: 'App Instance 2', + id: 'SZFO_04', + type: '' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'SZFO_05', + type: '' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'SZFO_06', + type: '' + }, + { + name: 'Phantom Gate', + id: 'SZFO_07', + type: '' }, { name: 'SentraLock', - caption: 'Lock-sentral-urals' + id: 'SZFO_08', + caption: 'Lock-sentral-urals', + type: '' }, { - name: 'Zero Trace' + name: 'Zero Trace', + id: 'SZFO_09', + type: '' } ] }, { name: 'ЮФО', + id: '03', apps: [ { name: 'Byte Sentinel', - caption: 'Byte 001' + caption: 'Byte 001', + id: 'UFO_01', + type: '' }, { - name: 'CryptoWall' + name: 'CryptoWall', + id: 'UFO_02', + type: '' }, { - name: 'Phantom Gate' + name: 'Phantom Gate', + id: 'UFO_03', + type: '' }, { name: 'SentraLock', - caption: 'Lock-sentral-urals' + caption: 'Lock-sentral-urals', + id: 'UFO_04', + type: '' }, { - name: 'Zero Trace' + name: 'Zero Trace', + id: 'UFO_05', + type: '' } ] }, { name: 'Южный Суверенный Федеральный Округ ФО', + status: 'Главная', + id: '04', apps: [ { name: 'Byte Sentinel', - caption: 'Byte 001' + caption: 'Byte 001', + id: 'USFO_01', + type: '' }, { name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' + id: 'USFO_02', + type: '' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'USFO_03', + type: '' + }, + { + name: 'App Instance 2', + id: 'USFO_04', + type: '' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'USFO_05', + type: '' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'USFO_06', + type: '' + }, + { + name: 'Phantom Gate', + id: 'USFO_07', + type: '' }, { name: 'SentraLock', - caption: 'Lock-sentral-urals' + caption: 'Lock-sentral-urals', + id: 'USFO_08', + type: '' }, { - name: 'Zero Trace' + name: 'Zero Trace', + id: 'USFO_09', + type: '' } - ], - main: true + ] }, { name: 'ПФО', + id: '05', apps: [ { name: 'Byte Sentinel', - caption: 'Byte 001' + caption: 'Byte 001', + id: 'PFO_01', + type: '' }, { - name: 'CryptoWall' + name: 'CryptoWall', + id: 'PFO_02', + type: '' }, { - name: 'Phantom Gate' + name: 'Phantom Gate', + id: 'PFO_03', + type: '' }, { name: 'SentraLock', - caption: 'Lock-sentral-urals' + caption: 'Lock-sentral-urals', + id: 'PFO_04', + type: '' }, { - name: 'Zero Trace' + name: 'Zero Trace', + id: 'PFO_05', + type: '' } ] }, { name: 'УФО', + id: '06', apps: [ { name: 'Byte Sentinel', - caption: 'Byte 001' + caption: 'Byte 001', + id: 'UFO_01', + type: '' }, { name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' + id: 'UFO_02', + type: '' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'UFO_03', + type: '' + }, + { + name: 'App Instance 2', + id: 'UFO_04', + type: '' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'UFO_05', + type: '' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'UFO_06', + type: '' + }, + { + name: 'Phantom Gate', + id: 'UFO_07', + type: '' }, { name: 'SentraLock', - caption: 'Lock-sentral-urals' + caption: 'Lock-sentral-urals', + id: 'UFO_08', + type: '' }, { - name: 'Zero Trace' + name: 'Zero Trace', + id: 'UFO_09', + type: '' } ] }, { name: 'СФО', + id: '07', apps: [ { name: 'Byte Sentinel', - caption: 'Byte 001' + caption: 'Byte 001', + id: 'SFO_01', + type: '' }, { - name: 'CryptoWall' + name: 'CryptoWall', + id: 'SFO_02', + type: '' }, { - name: 'Phantom Gate' + name: 'Phantom Gate', + id: 'SFO_03', + type: '' }, { name: 'SentraLock', - caption: 'Lock-sentral-urals' + caption: 'Lock-sentral-urals', + id: 'SFO_04', + type: '' }, { - name: 'Zero Trace' + name: 'Zero Trace', + id: 'SFO_05', + type: '' } ] }, { name: 'ДФО', + id: '08', apps: [ { name: 'Byte Sentinel', - caption: 'Byte 001' + caption: 'Byte 001', + id: 'DFO_01', + type: '' }, { name: 'CryptoWall', - apps: [ - { - name: 'App Instance 1', - caption: 'Instance Alias One' - }, - { - name: 'App Instance 2' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four' - } - ] - }, - { - name: 'Phantom Gate' + id: 'DFO_02', + type: '' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'DFO_03', + type: '' + }, + { + name: 'App Instance 2', + id: 'DFO_04', + type: '' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'DFO_05', + type: '' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'DFO_06', + type: '' + }, + { + name: 'Phantom Gate', + id: 'DFO_07', + type: '' }, { name: 'SentraLock', - caption: 'Lock-sentral-urals' + caption: 'Lock-sentral-urals', + id: 'DFO_08', + type: '' }, { - name: 'Zero Trace' + name: 'Zero Trace', + id: 'DFO_09', + type: '' } ] } ]; + + onSelectSite(site) { + console.log('onSelectSite: ', site); + } } @Component({ @@ -348,7 +478,4 @@ export class DevApp { componentColors = KbqComponentColors; modelValue: any = ''; - - activeSite; - activeApp; } diff --git a/packages/components/app-switcher/app-switcher-dropdown-site.scss b/packages/components/app-switcher/app-switcher-dropdown-site.scss index 7dbbeab18..3d875125a 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-site.scss +++ b/packages/components/app-switcher/app-switcher-dropdown-site.scss @@ -19,10 +19,6 @@ padding: var(--kbq-size-xxs) var(--kbq-size-m); - &:not(.kbq-disabled) { - cursor: pointer; - } - & .kbq-app-switcher-dropdown-item-trigger__icon, & .kbq-badge { margin-top: var(--kbq-size-3xs); diff --git a/packages/components/app-switcher/app-switcher-dropdown-site.ts b/packages/components/app-switcher/app-switcher-dropdown-site.ts index f07e59bea..32ae08e23 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-site.ts +++ b/packages/components/app-switcher/app-switcher-dropdown-site.ts @@ -11,8 +11,8 @@ import { KbqIcon } from '@koobiq/components/icon'; template: ` {{ site.name }} - @if (site.main) { - Главная + @if (site.status) { + {{ site.status }} } @if (isNested) { diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index 45c90ed1e..d60ad93a0 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -21,13 +21,17 @@ @if (!searchValue) {
-
Название площадки
- Главная +
{{ trigger.selectedSite?.name }}
+ @if (trigger.selectedSite?.status) { + + {{ trigger.selectedSite.status }} + + }
- @for (app of trigger.sites[0].apps; track app) { + @for (app of trigger.selectedSite?.apps; track app) {
@for (site of trigger.sites; track site) { - + @if (site.id !== trigger.selectedSite?.id) { +
+ } }
@@ -73,24 +77,25 @@ @for (app of activeSite?.apps; track app) { @if (app.apps) { - +
+ (click)="selectAppInSite(activeSite, app)" + >
} @else { - +
} }
@for (app of activeApp?.apps; track app) { - +
}
} diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index 12ef78d0b..589802d51 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -53,6 +53,23 @@ import { KbqAppSwitcherApp } from './app-switcher-app'; import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; +export interface KbaAppSwitcherApp { + name: string; + id: string | number; + type: string | number; + icon?: string; + caption?: string; + apps?: KbaAppSwitcherApp[]; +} + +export interface KbaAppSwitcherSite { + name: string; + id: string | number; + status?: string; + icon?: string; + apps: KbaAppSwitcherApp[]; +} + @Component({ standalone: true, selector: 'kbq-app-switcher', @@ -84,7 +101,10 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { protected readonly componentColors = KbqComponentColors; searchValue: string = ''; - withSites: boolean = true; + + get withSites(): boolean { + return !!this.trigger.sites.length; + } prefix = 'kbq-app-switcher'; @@ -92,8 +112,8 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { isTrapFocus: boolean = false; - activeSite; - activeApp; + protected activeSite; + protected activeApp; @ViewChild(KbqInput) input: KbqInput; @ViewChild('appSwitcherContent') appSwitcherContent: ElementRef; @@ -139,7 +159,13 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { this.hide(0); } - modelValue: any = ''; + selectAppInSite(site, app) { + this.trigger.selectedSite = site; + this.trigger.selectedApp = app; + + this.trigger.selectedSiteChanges.emit(site); + this.trigger.selectedAppChanges.emit(site); + } } export const KBQ_APP_SWITCHER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( @@ -169,7 +195,13 @@ export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER = { '(touchend)': 'handleTouchend()' } }) -export class KbqAppSwitcherTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { +export class KbqAppSwitcherTrigger< + S extends KbaAppSwitcherSite = KbaAppSwitcherSite, + A extends KbaAppSwitcherApp = KbaAppSwitcherApp + > + extends KbqPopUpTrigger + implements AfterContentInit, OnInit +{ protected scrollStrategy: () => ScrollStrategy = inject(KBQ_APP_SWITCHER_SCROLL_STRATEGY); // not used @@ -184,7 +216,46 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple @Input({ transform: booleanAttribute }) search: boolean; - @Input() sites; + @Input() + get sites(): S[] { + return this._parsedSites; + } + + set sites(value: S[]) { + console.log('set sites(value: S[]) {: '); + + this._parsedSites = []; + + value.forEach((site: S) => { + const newSite: S = { ...site }; + + const groupedApps: any = {}; + + site.apps.forEach((app) => { + if (groupedApps[app.type]) { + groupedApps[app.type].apps.push(app); + } else { + groupedApps[app.type] = { + name: app.type, + apps: [] + }; + } + }); + + if (Object.values(groupedApps).length > 1) { + newSite.apps = Object.values(groupedApps); + } + + this._parsedSites.push(newSite); + }); + } + + private _parsedSites: S[]; + + @Input() selectedSite: S; + + @Input() apps: A[]; + @Input() selectedApp: A; @Input({ transform: booleanAttribute }) get disabled(): boolean { @@ -213,6 +284,9 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple @Output('kbqVisibleChange') readonly visibleChange = new EventEmitter(); + @Output() readonly selectedSiteChanges = new EventEmitter(); + @Output() readonly selectedAppChanges = new EventEmitter(); + protected originSelector = '.kbq-app-switcher'; protected get overlayConfig(): OverlayConfig { From a0c344411923a25dcd91ae51270b4436d25daa42 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Tue, 30 Sep 2025 19:01:16 +0300 Subject: [PATCH 13/17] feat: added search --- .../components-dev/app-switcher/module.ts | 46 +++-- .../components-dev/app-switcher/styles.scss | 9 - .../components-dev/app-switcher/template.html | 2 +- .../app-switcher/_app-switcher-theme.scss | 6 +- .../app-switcher/app-switcher-dropdown-app.ts | 7 +- .../app-switcher-dropdown-site.ts | 3 +- .../components/app-switcher/app-switcher.html | 60 ++++-- .../app-switcher/app-switcher.module.ts | 4 +- .../components/app-switcher/app-switcher.scss | 18 ++ .../components/app-switcher/app-switcher.ts | 191 ++++++++++++------ ...p.scss => kbq-app-switcher-list-item.scss} | 34 ++-- ...r-app.ts => kbq-app-switcher-list-item.ts} | 36 ++-- .../components/app-switcher/public-api.ts | 2 +- 13 files changed, 264 insertions(+), 154 deletions(-) delete mode 100644 packages/components-dev/app-switcher/styles.scss rename packages/components/app-switcher/{app-switcher-app.scss => kbq-app-switcher-list-item.scss} (82%) rename packages/components/app-switcher/{app-switcher-app.ts => kbq-app-switcher-list-item.ts} (66%) diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index 6a2e0d13c..f05ceba52 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -15,7 +15,14 @@ import { PopoverExamplesModule } from 'packages/docs-examples/components/popover imports: [PopoverExamplesModule, KbqButtonModule, KbqIcon, KbqAppSwitcherModule], selector: 'dev-examples', template: ` - `, @@ -30,97 +37,94 @@ class DevExamples { { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', id: 'CFO_01', - type: 'type_1' + type: 'NAD' }, { name: 'Name', id: 'CFO_02', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - type: 'type_1' + type: 'NAD' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', id: 'CFO_03', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - type: 'type_1' + type: 'NAD' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', id: 'CFO_04', - type: 'type_1' + type: 'NAD' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', id: 'CFO_05', - type: 'type_2' + type: 'NE_NAD' }, { name: 'Name', id: 'CFO_06', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - type: 'type_2' + type: 'NE_NAD' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', id: 'CFO_07', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - type: 'type_2' + type: 'NE_NAD' }, { name: 'Name', id: 'CFO_08', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - type: 'type_2' + type: 'NE_NAD' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', id: 'CFO_09', - type: 'type_3' + type: 'SIEM' }, { name: 'Name', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', id: 'CFO_10', - type: 'type_3' + type: 'SIEM' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', id: 'CFO_11', - type: 'type_3' + type: 'SIEM' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', id: 'CFO_12', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - type: 'type_3' + type: 'SIEM' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', - id: 'CFO_13', - type: 'type_4' + id: 'CFO_13' }, { name: 'Name', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - id: 'CFO_14', - type: 'type_4' + id: 'CFO_14' }, { name: 'Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name Name', caption: 'Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption Caption', - id: 'CFO_15', - type: 'type_4' + id: 'CFO_15' } ] }, @@ -454,6 +458,10 @@ class DevExamples { onSelectSite(site) { console.log('onSelectSite: ', site); } + + onSelectApp(app) { + console.log('onSelectApp: ', app); + } } @Component({ diff --git a/packages/components-dev/app-switcher/styles.scss b/packages/components-dev/app-switcher/styles.scss deleted file mode 100644 index ef3032c61..000000000 --- a/packages/components-dev/app-switcher/styles.scss +++ /dev/null @@ -1,9 +0,0 @@ -.kbq-app-switcher__search-result { - max-width: 320px; - - display: flex; - flex-direction: column; - min-height: 0; - - padding: var(--kbq-size-xxs) var(--kbq-size-xxs) var(--kbq-size-s) var(--kbq-size-xxs); -} diff --git a/packages/components-dev/app-switcher/template.html b/packages/components-dev/app-switcher/template.html index 248ebbf6e..efcc808bf 100644 --- a/packages/components-dev/app-switcher/template.html +++ b/packages/components-dev/app-switcher/template.html @@ -11,7 +11,7 @@ - + diff --git a/packages/components/app-switcher/_app-switcher-theme.scss b/packages/components/app-switcher/_app-switcher-theme.scss index 50a47c0b6..e1c7935c6 100644 --- a/packages/components/app-switcher/_app-switcher-theme.scss +++ b/packages/components/app-switcher/_app-switcher-theme.scss @@ -9,12 +9,14 @@ } } - .kbq-app-switcher-sites { + .kbq-app-switcher__empty-search-result { + color: var(--kbq-foreground-contrast-secondary); } } @mixin kbq-app-switcher-typography() { - .kbq-app-switcher { + .kbq-app-switcher, + .kbq-app-switcher__empty-search-result { @include kbq-typography-level-to-styles-css-variables(typography, text-normal); } diff --git a/packages/components/app-switcher/app-switcher-dropdown-app.ts b/packages/components/app-switcher/app-switcher-dropdown-app.ts index 64a81382e..123151e78 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-app.ts +++ b/packages/components/app-switcher/app-switcher-dropdown-app.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@a import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; +import { KbaAppSwitcherApp } from './app-switcher'; @Component({ standalone: true, @@ -41,14 +42,12 @@ import { KbqIcon } from '@koobiq/components/icon'; class: 'kbq-app-switcher-dropdown-app', '[class.kbq-dropdown-item]': 'false' }, - imports: [ - KbqIcon - ], + imports: [KbqIcon], providers: [ { provide: KBQ_TITLE_TEXT_REF, useExisting: KbqAppSwitcherDropdownApp }, { provide: KbqDropdownItem, useExisting: KbqAppSwitcherDropdownApp } ] }) export class KbqAppSwitcherDropdownApp extends KbqDropdownItem { - @Input('kbq-app-switcher-dropdown-app') app; + @Input('kbq-app-switcher-dropdown-app') app: KbaAppSwitcherApp; } diff --git a/packages/components/app-switcher/app-switcher-dropdown-site.ts b/packages/components/app-switcher/app-switcher-dropdown-site.ts index 32ae08e23..a5407464f 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-site.ts +++ b/packages/components/app-switcher/app-switcher-dropdown-site.ts @@ -3,6 +3,7 @@ import { KbqBadgeModule } from '@koobiq/components/badge'; import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; +import { KbaAppSwitcherSite } from './app-switcher'; @Component({ standalone: true, @@ -40,5 +41,5 @@ import { KbqIcon } from '@koobiq/components/icon'; ] }) export class KbqAppSwitcherDropdownSite extends KbqDropdownItem { - @Input('kbq-app-switcher-dropdown-site') site; + @Input('kbq-app-switcher-dropdown-site') site: KbaAppSwitcherSite; } diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index d60ad93a0..eae6db407 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -1,13 +1,12 @@ @if (trigger.search) { -
+
@@ -18,7 +17,7 @@ }
- @if (!searchValue) { + @if (!searchControl.getRawValue()) {
{{ trigger.selectedSite?.name }}
@@ -32,17 +31,32 @@
@for (app of trigger.selectedSite?.apps; track app) { - + @if (!app.aliases) { + + } @else { +
- @if (!appSwitcherApp.collapsed) { - @for (alias of app.apps; track alias) { - + @if (!appSwitcherApp.collapsed) { + @for (alias of app.aliases; track alias) { + + } } } } @@ -76,8 +90,7 @@ @for (app of activeSite?.apps; track app) { - @if (app.apps) { - + @if (app.aliases) {
} @else {
@@ -94,8 +106,8 @@
- @for (app of activeApp?.apps; track app) { -
+ @for (alias of activeApp?.aliases; track alias) { +
}
} @@ -103,12 +115,16 @@
- @for (site of trigger.sites; track site) { - + @for (site of filteredSites | async; track site) { + @if (site.apps) { + + } @for (app of site.apps; track app) { - + } + } @empty { +
Ничего не найдено
}
diff --git a/packages/components/app-switcher/app-switcher.module.ts b/packages/components/app-switcher/app-switcher.module.ts index b219bdf49..9db696366 100644 --- a/packages/components/app-switcher/app-switcher.module.ts +++ b/packages/components/app-switcher/app-switcher.module.ts @@ -19,15 +19,15 @@ import { KbqAppSwitcher, KbqAppSwitcherTrigger } from './app-switcher'; -import { KbqAppSwitcherApp } from './app-switcher-app'; import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; +import { KbqAppSwitcherListItem } from './kbq-app-switcher-list-item'; @NgModule({ imports: [ KbqAppSwitcher, KbqAppSwitcherTrigger, - KbqAppSwitcherApp, + KbqAppSwitcherListItem, KbqAppSwitcherDropdownApp, KbqAppSwitcherDropdownSite, OverlayModule, diff --git a/packages/components/app-switcher/app-switcher.scss b/packages/components/app-switcher/app-switcher.scss index 89d542010..7aff2d593 100644 --- a/packages/components/app-switcher/app-switcher.scss +++ b/packages/components/app-switcher/app-switcher.scss @@ -24,6 +24,24 @@ padding-top: var(--kbq-size-xxs); } +.kbq-app-switcher__search-result { + max-width: 320px; + + display: flex; + flex-direction: column; + min-height: 0; + + padding: var(--kbq-size-xxs) var(--kbq-size-xxs) var(--kbq-size-s) var(--kbq-size-xxs); +} + +.kbq-app-switcher__empty-search-result { + display: flex; + justify-content: center; + align-items: center; + + height: 96px; +} + .kbq-app-switcher__app-container { display: flex; flex-direction: column; diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index 589802d51..7391f7b63 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -1,4 +1,3 @@ -import { CdkTrapFocus } from '@angular/cdk/a11y'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { CdkScrollable, @@ -7,13 +6,13 @@ import { OverlayConfig, ScrollStrategy } from '@angular/cdk/overlay'; +import { AsyncPipe } from '@angular/common'; import { AfterContentInit, AfterViewInit, ChangeDetectionStrategy, Component, Directive, - ElementRef, EventEmitter, InjectionToken, Input, @@ -28,10 +27,9 @@ import { numberAttribute } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { FormsModule } from '@angular/forms'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { KbqBadgeModule } from '@koobiq/components/badge'; import { - KbqComponentColors, KbqOptionModule, KbqPopUp, KbqPopUpTrigger, @@ -48,18 +46,20 @@ import { KbqInput, KbqInputModule } from '@koobiq/components/input'; import { defaultOffsetYWithArrow } from '@koobiq/components/popover'; import { KbqScrollbarModule } from '@koobiq/components/scrollbar'; import { Subscription, merge } from 'rxjs'; +import { map, startWith } from 'rxjs/operators'; import { kbqAppSwitcherAnimations } from './app-switcher-animations'; -import { KbqAppSwitcherApp } from './app-switcher-app'; import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; +import { KbqAppSwitcherListItem } from './kbq-app-switcher-list-item'; export interface KbaAppSwitcherApp { name: string; id: string | number; - type: string | number; + type?: string | number; icon?: string; caption?: string; - apps?: KbaAppSwitcherApp[]; + aliases?: KbaAppSwitcherApp[]; + link?: string; } export interface KbaAppSwitcherSite { @@ -70,6 +70,22 @@ export interface KbaAppSwitcherSite { apps: KbaAppSwitcherApp[]; } +export function defaultGroupBy(groupedApps: Record>, app: KbaAppSwitcherApp) { + const appType = app.type ? app.type.toString() : 'untyped'; + + if (groupedApps[appType]) { + groupedApps[appType].aliases!.push(app); + } else { + groupedApps[appType] = { + name: appType, + aliases: [] + }; + } +} + +export const MAX_APPS_FOR_ENABLE_SEARCH = 7; +export const MAX_APPS_FOR_ENABLE_GROUPING = 3; + @Component({ standalone: true, selector: 'kbq-app-switcher', @@ -91,16 +107,21 @@ export interface KbaAppSwitcherSite { KbqDropdownModule, KbqAppSwitcherDropdownApp, KbqAppSwitcherDropdownSite, - KbqAppSwitcherApp, + KbqAppSwitcherListItem, KbqScrollbarModule, - KbqOptionModule + KbqOptionModule, + ReactiveFormsModule, + AsyncPipe ], animations: [kbqAppSwitcherAnimations.state] }) export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { - protected readonly componentColors = KbqComponentColors; + readonly searchControl = new FormControl(''); - searchValue: string = ''; + readonly filteredSites = this.searchControl.valueChanges.pipe( + startWith(''), + map((query) => this.filterSites(query)) + ); get withSites(): boolean { return !!this.trigger.sites.length; @@ -112,24 +133,17 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { isTrapFocus: boolean = false; - protected activeSite; - protected activeApp; + protected activeSite: KbaAppSwitcherSite; + protected activeApp: KbaAppSwitcherApp; @ViewChild(KbqInput) input: KbqInput; - @ViewChild('appSwitcherContent') appSwitcherContent: ElementRef; - @ViewChild('appSwitcher') elementRef: ElementRef; @ViewChild('otherSites') otherSites: KbqDropdownTrigger; - @ViewChild(CdkTrapFocus) cdkTrapFocus: CdkTrapFocus; ngAfterViewInit() { if (this.input) { this.input.focus(); } - if (!this.appSwitcherContent) return; - - this.cdkTrapFocus.focusTrap.focusFirstTabbableElement(); - this.visibleChange.subscribe((state) => { if (this.offset !== null && state) { applyPopupMargins( @@ -150,21 +164,34 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { this.isTrapFocus = isTrapFocus; } - onSearchChange(value: string) { - this.searchValue = value; - console.log('onSearchChange: ', value); - } - onEscape() { this.hide(0); } - selectAppInSite(site, app) { + selectAppInSite(site, app: KbaAppSwitcherApp) { this.trigger.selectedSite = site; this.trigger.selectedApp = app; this.trigger.selectedSiteChanges.emit(site); - this.trigger.selectedAppChanges.emit(site); + this.trigger.selectedAppChanges.emit(app); + } + + private filterSites(query: string | null): KbaAppSwitcherSite[] { + const filteredSites = structuredClone(this.trigger.originalSites); + + return query + ? filteredSites.filter((site) => { + const filteredApps = site.apps.filter((app) => app.name.toLowerCase().includes(query.toLowerCase())); + + if (filteredApps.length) { + site.apps = filteredApps; + + return true; + } + + return false; + }) + : filteredSites; } } @@ -172,12 +199,12 @@ export const KBQ_APP_SWITCHER_SCROLL_STRATEGY = new InjectionToken<() => ScrollS 'kbq-app-switcher-scroll-strategy' ); -/** @docs-private */ +// @docs-private export function kbqAppSwitcherScrollStrategyFactory(overlay: Overlay): () => ScrollStrategy { return () => overlay.scrollStrategies.reposition({ scrollThrottle: 20 }); } -/** @docs-private */ +// @docs-private export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER = { provide: KBQ_APP_SWITCHER_SCROLL_STRATEGY, deps: [Overlay], @@ -195,13 +222,7 @@ export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER = { '(touchend)': 'handleTouchend()' } }) -export class KbqAppSwitcherTrigger< - S extends KbaAppSwitcherSite = KbaAppSwitcherSite, - A extends KbaAppSwitcherApp = KbaAppSwitcherApp - > - extends KbqPopUpTrigger - implements AfterContentInit, OnInit -{ +export class KbqAppSwitcherTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { protected scrollStrategy: () => ScrollStrategy = inject(KBQ_APP_SWITCHER_SCROLL_STRATEGY); // not used @@ -214,48 +235,98 @@ export class KbqAppSwitcherTrigger< footer: string | TemplateRef; private closeOnScroll: null; - @Input({ transform: booleanAttribute }) search: boolean; + get search(): boolean { + return this.appsCount > MAX_APPS_FOR_ENABLE_SEARCH; + } + + get appsCount(): number { + return this.originalSites.reduce((acc, site) => acc + site.apps.length, 0); + } @Input() - get sites(): S[] { + get sites(): KbaAppSwitcherSite[] { return this._parsedSites; } - set sites(value: S[]) { - console.log('set sites(value: S[]) {: '); + set sites(value: KbaAppSwitcherSite[]) { + this.originalSites = value; this._parsedSites = []; - value.forEach((site: S) => { - const newSite: S = { ...site }; + value.forEach((site: KbaAppSwitcherSite) => { + const newSite: KbaAppSwitcherSite = { ...site, apps: [] }; + + const groupedApps: Record = {}; - const groupedApps: any = {}; + // todo refactor + if (site.apps.length > MAX_APPS_FOR_ENABLE_GROUPING) { + site.apps.forEach((app) => { + this.groupBy(groupedApps, app); + }); - site.apps.forEach((app) => { - if (groupedApps[app.type]) { - groupedApps[app.type].apps.push(app); - } else { - groupedApps[app.type] = { - name: app.type, - apps: [] - }; + if (Object.values(groupedApps).length > 1) { + newSite.apps = Object.values(groupedApps).filter((app) => app.name !== 'untyped'); } - }); - if (Object.values(groupedApps).length > 1) { - newSite.apps = Object.values(groupedApps); + if (groupedApps.untyped?.aliases?.length) { + newSite.apps.push(...groupedApps.untyped.aliases); + } } this._parsedSites.push(newSite); }); } - private _parsedSites: S[]; + private _parsedSites: KbaAppSwitcherSite[]; + originalSites: KbaAppSwitcherSite[]; + + // Function to group the apps by type. The first argument is an object with app types. The second is an app + @Input() + get groupBy() { + return this._groupBy; + } + + set groupBy(fn: (groupedApps: Record>, app: KbaAppSwitcherApp) => void) { + if (typeof fn !== 'function') { + throw new Error('The argument must be a function'); + } + + this._groupBy = fn; + } + + private _groupBy = defaultGroupBy; + + @Input() + get selectedSite(): KbaAppSwitcherSite { + return this._parsedSelectedSite; + } + + set selectedSite(value: KbaAppSwitcherSite) { + const originValue = this.originalSites.find((site) => value.id === site.id) as KbaAppSwitcherSite; + const newSite: KbaAppSwitcherSite = { ...originValue, apps: [] }; + const groupedApps: Record = {}; + + if (originValue.apps.length > MAX_APPS_FOR_ENABLE_GROUPING) { + originValue.apps.forEach((app) => { + this.groupBy(groupedApps, app); + }); + + if (Object.values(groupedApps).length > 1) { + newSite.apps = Object.values(groupedApps).filter((app) => app.name !== 'untyped'); + } + + if (groupedApps.untyped?.aliases?.length) { + newSite.apps.push(...groupedApps.untyped.aliases); + } + } + + this._parsedSelectedSite = newSite; + } - @Input() selectedSite: S; + private _parsedSelectedSite: KbaAppSwitcherSite; - @Input() apps: A[]; - @Input() selectedApp: A; + @Input() apps: KbaAppSwitcherApp[]; + @Input() selectedApp: KbaAppSwitcherApp; @Input({ transform: booleanAttribute }) get disabled(): boolean { @@ -284,8 +355,8 @@ export class KbqAppSwitcherTrigger< @Output('kbqVisibleChange') readonly visibleChange = new EventEmitter(); - @Output() readonly selectedSiteChanges = new EventEmitter(); - @Output() readonly selectedAppChanges = new EventEmitter(); + @Output() readonly selectedSiteChanges = new EventEmitter(); + @Output() readonly selectedAppChanges = new EventEmitter(); protected originSelector = '.kbq-app-switcher'; diff --git a/packages/components/app-switcher/app-switcher-app.scss b/packages/components/app-switcher/kbq-app-switcher-list-item.scss similarity index 82% rename from packages/components/app-switcher/app-switcher-app.scss rename to packages/components/app-switcher/kbq-app-switcher-list-item.scss index 41f3b1f82..dd78887a5 100644 --- a/packages/components/app-switcher/app-switcher-app.scss +++ b/packages/components/app-switcher/kbq-app-switcher-list-item.scss @@ -1,7 +1,7 @@ @use '../core/styles/common'; @use '../core/styles/common/tokens'; -.kbq-app-switcher-app { +.kbq-app-switcher-list-item { position: relative; display: flex; @@ -59,7 +59,7 @@ } } -.kbq-app-switcher-app__toggle { +.kbq-app-switcher-list-item__toggle { position: absolute; display: none; @@ -85,47 +85,47 @@ } } -.kbq-app-switcher-app__icon { +.kbq-app-switcher-list-item__icon { padding-top: var(--kbq-size-s); padding-right: var(--kbq-size-m); } -.kbq-app-switcher-app__container { +.kbq-app-switcher-list-item__container { max-width: 100%; min-width: 0; align-self: center; - & .kbq-app-switcher-app__name { + & .kbq-app-switcher-list-item__name { @include common.kbq-truncate-line(); } - & .kbq-app-switcher-app__caption { + & .kbq-app-switcher-list-item__caption { margin-left: var(--kbq-size-3xs); @include common.kbq-truncate-line(); } } -@mixin kbq-app-switcher-app-theme() { +@mixin kbq-app-switcher-list-item-theme() { .kbq-app-switcher-site_nested:after { border-left-color: var(--kbq-line-contrast-less); border-bottom-color: var(--kbq-line-contrast-less); } - .kbq-app-switcher-app__caption { + .kbq-app-switcher-list-item__caption { color: var(--kbq-foreground-contrast-secondary); } - .kbq-app-switcher-app { + .kbq-app-switcher-list-item { &:hover, &.kbq-hover, &.kbq-focused { - &:has(.kbq-app-switcher-app__toggle) .kbq-app-switcher-app__icon { + &:has(.kbq-app-switcher-list-item__toggle) .kbq-app-switcher-list-item__icon { visibility: hidden; } - & .kbq-app-switcher-app__toggle { + & .kbq-app-switcher-list-item__toggle { display: flex; } } @@ -134,7 +134,7 @@ &.kbq-hover { background: var(--kbq-states-background-transparent-hover); - & .kbq-app-switcher-app__toggle { + & .kbq-app-switcher-list-item__toggle { background: var(--kbq-states-background-transparent-hover); } } @@ -168,15 +168,15 @@ } } -@mixin kbq-app-switcher-app-typography() { - .kbq-app-switcher-app__name { +@mixin kbq-app-switcher-list-item-typography() { + .kbq-app-switcher-list-item__name { @include tokens.kbq-typography-level-to-styles-css-variables(typography, text-normal-medium); } - .kbq-app-switcher-app__caption { + .kbq-app-switcher-list-item__caption { @include tokens.kbq-typography-level-to-styles-css-variables(typography, text-compact); } } -@include kbq-app-switcher-app-theme(); -@include kbq-app-switcher-app-typography(); +@include kbq-app-switcher-list-item-theme(); +@include kbq-app-switcher-list-item-typography(); diff --git a/packages/components/app-switcher/app-switcher-app.ts b/packages/components/app-switcher/kbq-app-switcher-list-item.ts similarity index 66% rename from packages/components/app-switcher/app-switcher-app.ts rename to packages/components/app-switcher/kbq-app-switcher-list-item.ts index be2ef1a95..a419e6d80 100644 --- a/packages/components/app-switcher/app-switcher-app.ts +++ b/packages/components/app-switcher/kbq-app-switcher-list-item.ts @@ -2,15 +2,16 @@ import { booleanAttribute, ChangeDetectionStrategy, Component, Input, ViewEncaps import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; +import { KbaAppSwitcherApp } from './app-switcher'; @Component({ standalone: true, - selector: '[kbq-app-switcher-app]', + selector: '[kbq-app-switcher-list-item]', exportAs: 'kbqAppSwitcherApp', template: ` - + @@ -22,44 +23,47 @@ import { KbqIcon } from '@koobiq/components/icon'; [attr.fill]="'white'" /> -
-
{{ app.name }}
+
+
{{ app.name }}
@if (app.caption) { -
{{ app.caption }}
+
{{ app.caption }}
}
@if (toggle) { -
+
} `, - styleUrls: ['app-switcher-app.scss'], + styleUrls: ['kbq-app-switcher-list-item.scss'], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { - class: 'kbq-app-switcher-app', - '[class.kbq-dropdown-item]': 'false' + class: 'kbq-app-switcher-list-item', + '[class.kbq-dropdown-item]': 'false', + '(click)': 'clickHandler($event)' }, imports: [ KbqIcon ], providers: [ - { provide: KBQ_TITLE_TEXT_REF, useExisting: KbqAppSwitcherApp }, - { provide: KbqDropdownItem, useExisting: KbqAppSwitcherApp } + { provide: KBQ_TITLE_TEXT_REF, useExisting: KbqAppSwitcherListItem }, + { provide: KbqDropdownItem, useExisting: KbqAppSwitcherListItem } ] }) -export class KbqAppSwitcherApp extends KbqDropdownItem { - @Input() app; +export class KbqAppSwitcherListItem extends KbqDropdownItem { + @Input() app: KbaAppSwitcherApp; @Input({ transform: booleanAttribute }) toggle = false; @Input() collapsed: boolean = false; clickHandler(event: MouseEvent) { - event.stopPropagation(); - event.preventDefault(); + if (this.toggle) { + event.stopPropagation(); + event.preventDefault(); - this.collapsed = !this.collapsed; + this.collapsed = !this.collapsed; + } } } diff --git a/packages/components/app-switcher/public-api.ts b/packages/components/app-switcher/public-api.ts index 81c99bf33..59b6e9f0d 100644 --- a/packages/components/app-switcher/public-api.ts +++ b/packages/components/app-switcher/public-api.ts @@ -1,6 +1,6 @@ export * from './app-switcher'; export * from './app-switcher-animations'; -export * from './app-switcher-app'; export * from './app-switcher-dropdown-app'; export * from './app-switcher-dropdown-site'; export * from './app-switcher.module'; +export * from './kbq-app-switcher-list-item'; From ac2892610d5be081a1dc31f20a9c1d60db84d72e Mon Sep 17 00:00:00 2001 From: lskramarov Date: Tue, 30 Sep 2025 19:25:32 +0300 Subject: [PATCH 14/17] feat: added example --- apps/docs/src/app/structure.ts | 12 +++ .../components-dev/app-switcher/module.ts | 27 +++--- .../app-switcher/app-switcher.ru.md | 11 +++ .../app-switcher-overview-example.ts | 94 +++++++++++++++++++ .../components/app-switcher/index.ts | 14 +++ .../components/app-switcher/ng-package.json | 5 + tools/cspell-locales/ru.json | 1 + 7 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts create mode 100644 packages/docs-examples/components/app-switcher/index.ts create mode 100644 packages/docs-examples/components/app-switcher/ng-package.json diff --git a/apps/docs/src/app/structure.ts b/apps/docs/src/app/structure.ts index 5a56f5953..6559d03af 100644 --- a/apps/docs/src/app/structure.ts +++ b/apps/docs/src/app/structure.ts @@ -14,6 +14,7 @@ export enum DocsStructureItemId { // Components Accordion = 'accordion', ActionsPanel = 'actions-panel', + AppSwitcher = 'app-switcher', AgGrid = 'ag-grid', Alert = 'alert', Autocomplete = 'autocomplete', @@ -278,6 +279,17 @@ const structure: DocsStructure = makeStructure({ apiId: 'actions-panel', hasExamples: true }, + { + id: DocsStructureItemId.AppSwitcher, + name: { + ru: 'App Switcher', + en: 'App Switcher' + }, + svgPreview: 'app-switcher', + hasApi: true, + apiId: 'app-switcher', + hasExamples: false + }, { id: DocsStructureItemId.AgGrid, name: { diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index f05ceba52..7aa69fcf3 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -8,23 +8,25 @@ import { KbqComponentColors, KbqOptionModule } from '@koobiq/components/core'; import { KbqDividerModule } from '@koobiq/components/divider'; import { KbqDropdownModule } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; -import { PopoverExamplesModule } from 'packages/docs-examples/components/popover'; +import { AppSwitcherExamplesModule } from '../../docs-examples/components/app-switcher'; @Component({ standalone: true, - imports: [PopoverExamplesModule, KbqButtonModule, KbqIcon, KbqAppSwitcherModule], + imports: [AppSwitcherExamplesModule, KbqButtonModule, KbqIcon, KbqAppSwitcherModule], selector: 'dev-examples', template: ` - + + + + + + + + + + + + `, changeDetection: ChangeDetectionStrategy.OnPush }) @@ -467,7 +469,6 @@ class DevExamples { @Component({ standalone: true, selector: 'dev-app', - styleUrls: ['./styles.scss'], templateUrl: './template.html', imports: [ A11yModule, diff --git a/packages/components/app-switcher/app-switcher.ru.md b/packages/components/app-switcher/app-switcher.ru.md index e69de29bb..62d58f61d 100644 --- a/packages/components/app-switcher/app-switcher.ru.md +++ b/packages/components/app-switcher/app-switcher.ru.md @@ -0,0 +1,11 @@ +Меню для переключения между приложениями и площадками. + + + +### Несколько площадок + +Когда в системе представлено несколько площадок, приложения выбранной показаны сверху списка и показано название площадки. Другие площадки представлены в конце с свернутом виде. 4 или более приложений одного типа в пределах площадки объединяются в группу. Главная площадка помечается бейджем. + +Поиск появляется, если доступно больше 7 приложений. Результаты представлены в виде плоского списка. + + diff --git a/packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts b/packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts new file mode 100644 index 000000000..93b90109e --- /dev/null +++ b/packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts @@ -0,0 +1,94 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { KbaAppSwitcherSite, KbqAppSwitcherTrigger } from '@koobiq/components/app-switcher'; +import { KbqAutocompleteModule } from '@koobiq/components/autocomplete'; +import { KbqButtonModule } from '@koobiq/components/button'; +import { KbqFormsModule } from '@koobiq/components/core'; +import { KbqFormFieldModule } from '@koobiq/components/form-field'; +import { KbqIcon } from '@koobiq/components/icon'; +import { KbqInputModule } from '@koobiq/components/input'; + +/** + * @title app-switcher + */ +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + selector: 'app-switcher-overview-example', + imports: [ + KbqFormFieldModule, + KbqAutocompleteModule, + KbqInputModule, + ReactiveFormsModule, + KbqFormsModule, + KbqAppSwitcherTrigger, + KbqButtonModule, + KbqIcon + ], + template: ` + + ` +}) +export class AppSwitcherOverviewExample { + sites: KbaAppSwitcherSite[] = [ + { + name: 'СЗФО', + id: '02', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'SZFO_01', + icon: '', + type: '' + }, + { + name: 'CryptoWall', + id: 'SZFO_02', + type: '' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'SZFO_03', + type: '' + }, + { + name: 'App Instance 2', + id: 'SZFO_04', + type: '' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'SZFO_05', + type: '' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'SZFO_06', + type: '' + }, + { + name: 'Phantom Gate', + id: 'SZFO_07', + type: '' + }, + { + name: 'SentraLock', + id: 'SZFO_08', + caption: 'Lock-sentral-urals', + type: '' + }, + { + name: 'Zero Trace', + id: 'SZFO_09', + type: '' + } + ] + } + ]; +} diff --git a/packages/docs-examples/components/app-switcher/index.ts b/packages/docs-examples/components/app-switcher/index.ts new file mode 100644 index 000000000..3aedf311b --- /dev/null +++ b/packages/docs-examples/components/app-switcher/index.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { AppSwitcherOverviewExample } from './app-switcher-overview/app-switcher-overview-example'; + +export { AppSwitcherOverviewExample }; + +const EXAMPLES = [ + AppSwitcherOverviewExample +]; + +@NgModule({ + imports: EXAMPLES, + exports: EXAMPLES +}) +export class AppSwitcherExamplesModule {} diff --git a/packages/docs-examples/components/app-switcher/ng-package.json b/packages/docs-examples/components/app-switcher/ng-package.json new file mode 100644 index 000000000..bebf62dcb --- /dev/null +++ b/packages/docs-examples/components/app-switcher/ng-package.json @@ -0,0 +1,5 @@ +{ + "lib": { + "entryFile": "index.ts" + } +} diff --git a/tools/cspell-locales/ru.json b/tools/cspell-locales/ru.json index 8bde3cbd6..33751a299 100644 --- a/tools/cspell-locales/ru.json +++ b/tools/cspell-locales/ru.json @@ -18,6 +18,7 @@ "бейджа", "бейджами", "бейджей", + "бейджем", "бейджи", "булевый", "валидаторами", From 103bd18ce98588178645c4b36115fdc61ef8f5b8 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Tue, 30 Sep 2025 19:35:39 +0300 Subject: [PATCH 15/17] fix: build --- apps/docs/src/sitemap.xml | 12 + packages/docs-examples/example-module.ts | 14 + tools/api-extractor/config.json | 1 + .../components/app-switcher.api.md | 272 ++++++++++++++++++ .../components/dropdown.api.md | 12 +- .../components/popover.api.md | 3 + tools/public_api_guard/components/tree.api.md | 2 +- 7 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 tools/public_api_guard/components/app-switcher.api.md diff --git a/apps/docs/src/sitemap.xml b/apps/docs/src/sitemap.xml index 5ad801f93..ba03cbb57 100644 --- a/apps/docs/src/sitemap.xml +++ b/apps/docs/src/sitemap.xml @@ -72,6 +72,18 @@ https://koobiq.io/ru/components/actions-panel/api + + https://koobiq.io/en/components/app-switcher/overview + + + https://koobiq.io/ru/components/app-switcher/overview + + + https://koobiq.io/en/components/app-switcher/api + + + https://koobiq.io/ru/components/app-switcher/api + https://koobiq.io/en/components/ag-grid/overview diff --git a/packages/docs-examples/example-module.ts b/packages/docs-examples/example-module.ts index c98a4aa2b..fa28633bf 100644 --- a/packages/docs-examples/example-module.ts +++ b/packages/docs-examples/example-module.ts @@ -288,6 +288,18 @@ export const EXAMPLE_COMPONENTS: {[id: string]: LiveExample} = { "primaryFile": "alert-variants-example.ts", "importPath": "components/alert" }, + "app-switcher-overview": { + "packagePath": "components/app-switcher/app-switcher-overview", + "title": "app-switcher", + "componentName": "AppSwitcherOverviewExample", + "files": [ + "app-switcher-overview-example.ts" + ], + "selector": "app-switcher-overview-example", + "additionalComponents": [], + "primaryFile": "app-switcher-overview-example.ts", + "importPath": "components/app-switcher" + }, "autocomplete-overview": { "packagePath": "components/autocomplete/autocomplete-overview", "title": "Autocomplete", @@ -5279,6 +5291,8 @@ return import('@koobiq/docs-examples/components/alert'); return import('@koobiq/docs-examples/components/alert'); case 'alert-variants': return import('@koobiq/docs-examples/components/alert'); + case 'app-switcher-overview': +return import('@koobiq/docs-examples/components/app-switcher'); case 'autocomplete-overview': return import('@koobiq/docs-examples/components/autocomplete'); case 'badge-content': diff --git a/tools/api-extractor/config.json b/tools/api-extractor/config.json index a2dd29ef7..be4c140dc 100644 --- a/tools/api-extractor/config.json +++ b/tools/api-extractor/config.json @@ -6,6 +6,7 @@ ], "components": [ "actions-panel", + "app-switcher", "autocomplete", "button", "button-toggle", diff --git a/tools/public_api_guard/components/app-switcher.api.md b/tools/public_api_guard/components/app-switcher.api.md new file mode 100644 index 000000000..5a17d81ba --- /dev/null +++ b/tools/public_api_guard/components/app-switcher.api.md @@ -0,0 +1,272 @@ +## API Report File for "koobiq" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AfterContentInit } from '@angular/core'; +import { AfterViewInit } from '@angular/core'; +import { AnimationTriggerMetadata } from '@angular/animations'; +import { CdkScrollable } from '@angular/cdk/overlay'; +import { EventEmitter } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import * as i0 from '@angular/core'; +import * as i10 from '@angular/common'; +import * as i11 from '@koobiq/components/form-field'; +import * as i12 from '@koobiq/components/input'; +import * as i13 from '@angular/forms'; +import * as i5 from '@angular/cdk/overlay'; +import * as i6 from '@koobiq/components/button'; +import * as i7 from '@angular/cdk/a11y'; +import * as i8 from '@koobiq/components/icon'; +import * as i9 from '@angular/cdk/observers'; +import { InjectionToken } from '@angular/core'; +import { KbqDropdownItem } from '@koobiq/components/dropdown'; +import { KbqDropdownTrigger } from '@koobiq/components/dropdown'; +import { KbqInput } from '@koobiq/components/input'; +import { KbqPopUp } from '@koobiq/components/core'; +import { KbqPopUpTrigger } from '@koobiq/components/core'; +import { Observable } from 'rxjs'; +import { OnInit } from '@angular/core'; +import { Overlay } from '@angular/cdk/overlay'; +import { OverlayConfig } from '@angular/cdk/overlay'; +import { PopUpSizes } from '@koobiq/components/core'; +import { ScrollStrategy } from '@angular/cdk/overlay'; +import { Subscription } from 'rxjs'; +import { TemplateRef } from '@angular/core'; +import { Type } from '@angular/core'; + +// @public (undocumented) +export function defaultGroupBy(groupedApps: Record>, app: KbaAppSwitcherApp): void; + +// @public (undocumented) +export interface KbaAppSwitcherApp { + // (undocumented) + aliases?: KbaAppSwitcherApp[]; + // (undocumented) + caption?: string; + // (undocumented) + icon?: string; + // (undocumented) + id: string | number; + // (undocumented) + link?: string; + // (undocumented) + name: string; + // (undocumented) + type?: string | number; +} + +// @public (undocumented) +export interface KbaAppSwitcherSite { + // (undocumented) + apps: KbaAppSwitcherApp[]; + // (undocumented) + icon?: string; + // (undocumented) + id: string | number; + // (undocumented) + name: string; + // (undocumented) + status?: string; +} + +// @public (undocumented) +export const KBQ_APP_SWITCHER_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>; + +// @public (undocumented) +export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER: { + provide: InjectionToken<() => ScrollStrategy>; + deps: (typeof Overlay)[]; + useFactory: typeof kbqAppSwitcherScrollStrategyFactory; +}; + +// @public (undocumented) +export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { + // (undocumented) + protected activeApp: KbaAppSwitcherApp; + // (undocumented) + protected activeSite: KbaAppSwitcherSite; + // (undocumented) + readonly filteredSites: Observable; + // (undocumented) + input: KbqInput; + // (undocumented) + isTrapFocus: boolean; + // (undocumented) + ngAfterViewInit(): void; + // (undocumented) + onEscape(): void; + // (undocumented) + otherSites: KbqDropdownTrigger; + // (undocumented) + prefix: string; + // (undocumented) + readonly searchControl: FormControl; + // (undocumented) + selectAppInSite(site: any, app: KbaAppSwitcherApp): void; + // (undocumented) + trigger: KbqAppSwitcherTrigger; + // (undocumented) + updateClassMap(placement: string, customClass: string, size: PopUpSizes): void; + // (undocumented) + updateTrapFocus(isTrapFocus: boolean): void; + // (undocumented) + get withSites(): boolean; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export const kbqAppSwitcherAnimations: { + readonly state: AnimationTriggerMetadata; +}; + +// @public (undocumented) +export class KbqAppSwitcherDropdownApp extends KbqDropdownItem { + // (undocumented) + app: KbaAppSwitcherApp; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export class KbqAppSwitcherDropdownSite extends KbqDropdownItem { + // (undocumented) + site: KbaAppSwitcherSite; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export class KbqAppSwitcherListItem extends KbqDropdownItem { + // (undocumented) + app: KbaAppSwitcherApp; + // (undocumented) + clickHandler(event: MouseEvent): void; + // (undocumented) + collapsed: boolean; + // (undocumented) + static ngAcceptInputType_toggle: unknown; + // (undocumented) + toggle: boolean; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export class KbqAppSwitcherModule { + // (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 + // Warning: (ae-forgotten-export) The symbol "i3" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i4" needs to be exported by the entry point index.d.ts + // + // (undocumented) + static ɵmod: i0.ɵɵNgModuleDeclaration; +} + +// @public (undocumented) +export function kbqAppSwitcherScrollStrategyFactory(overlay: Overlay): () => ScrollStrategy; + +// @public (undocumented) +export class KbqAppSwitcherTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { + // (undocumented) + apps: KbaAppSwitcherApp[]; + // (undocumented) + get appsCount(): number; + // (undocumented) + arrow: boolean; + // (undocumented) + backdropClass: string; + // (undocumented) + closingActions(): Observable; + // (undocumented) + content: string | TemplateRef; + // (undocumented) + customClass: string; + // (undocumented) + get disabled(): boolean; + set disabled(value: boolean); + // (undocumented) + footer: string | TemplateRef; + // (undocumented) + getOverlayHandleComponentType(): Type; + // (undocumented) + get groupBy(): (groupedApps: Record>, app: KbaAppSwitcherApp) => void; + set groupBy(fn: (groupedApps: Record>, app: KbaAppSwitcherApp) => void); + // (undocumented) + get hasClickTrigger(): boolean; + // (undocumented) + header: string | TemplateRef; + // (undocumented) + static ngAcceptInputType_disabled: unknown; + // (undocumented) + static ngAcceptInputType_offset: unknown; + // (undocumented) + ngAfterContentInit(): void; + // (undocumented) + ngOnInit(): void; + // (undocumented) + offset: number | null; + // (undocumented) + originalSites: KbaAppSwitcherSite[]; + // (undocumented) + protected originSelector: string; + // (undocumented) + protected get overlayConfig(): OverlayConfig; + // (undocumented) + readonly placementChange: EventEmitter; + // (undocumented) + protected preventClosingByInnerScrollSubscription: Subscription; + // (undocumented) + protected scrollStrategy: () => ScrollStrategy; + // (undocumented) + get search(): boolean; + // (undocumented) + selectedApp: KbaAppSwitcherApp; + // (undocumented) + readonly selectedAppChanges: EventEmitter; + // (undocumented) + get selectedSite(): KbaAppSwitcherSite; + set selectedSite(value: KbaAppSwitcherSite); + // (undocumented) + readonly selectedSiteChanges: EventEmitter; + // (undocumented) + get sites(): KbaAppSwitcherSite[]; + set sites(value: KbaAppSwitcherSite[]); + // (undocumented) + trigger: string; + // (undocumented) + updateClassMap(newPlacement?: string): void; + // (undocumented) + updateData(): void; + updatePosition(reapplyPosition?: boolean): void; + // (undocumented) + readonly visibleChange: EventEmitter; + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export const MAX_APPS_FOR_ENABLE_GROUPING = 3; + +// @public (undocumented) +export const MAX_APPS_FOR_ENABLE_SEARCH = 7; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/tools/public_api_guard/components/dropdown.api.md b/tools/public_api_guard/components/dropdown.api.md index 08fda6add..0f2f6fb38 100644 --- a/tools/public_api_guard/components/dropdown.api.md +++ b/tools/public_api_guard/components/dropdown.api.md @@ -122,7 +122,7 @@ export class KbqDropdown implements AfterContentInit, KbqDropdownPanel, OnInit, get yPosition(): DropdownPositionY; set yPosition(value: DropdownPositionY); // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -210,7 +210,7 @@ export class KbqDropdownModule { // Warning: (ae-forgotten-export) The symbol "i4" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public @@ -249,6 +249,14 @@ export interface KbqDropdownPanel { yPosition: DropdownPositionY; } +// @public (undocumented) +export class kbqDropdownStaticContent { + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + // @public export class KbqDropdownTrigger implements AfterContentInit, OnDestroy { constructor(overlay: Overlay, elementRef: ElementRef, viewContainerRef: ViewContainerRef, scrollStrategy: any, parent: KbqDropdown, dropdownItemInstance: KbqDropdownItem, _dir: Directionality, changeDetectorRef: ChangeDetectorRef, focusMonitor?: FocusMonitor | undefined); diff --git a/tools/public_api_guard/components/popover.api.md b/tools/public_api_guard/components/popover.api.md index c23c1fb43..a6517b5e2 100644 --- a/tools/public_api_guard/components/popover.api.md +++ b/tools/public_api_guard/components/popover.api.md @@ -33,6 +33,9 @@ import { Subject } from 'rxjs'; import { TemplateRef } from '@angular/core'; import { Type } from '@angular/core'; +// @public (undocumented) +export const defaultOffsetYWithArrow = 8; + // @public export function getKbqPopoverInvalidPositionError(position: string): Error; diff --git a/tools/public_api_guard/components/tree.api.md b/tools/public_api_guard/components/tree.api.md index 71e0ada21..c4277317c 100644 --- a/tools/public_api_guard/components/tree.api.md +++ b/tools/public_api_guard/components/tree.api.md @@ -411,7 +411,7 @@ export class KbqTreeNodeToggleBaseDirective { // @public (undocumented) export class KbqTreeNodeToggleComponent extends KbqTreeNodeToggleBaseDirective { // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration, "kbq-tree-node-toggle", ["kbqTreeNodeToggle"], {}, {}, never, never, false, never>; + static ɵcmp: i0.ɵɵComponentDeclaration, "kbq-tree-node-toggle", ["kbqTreeNodeToggle"], {}, {}, never, ["*"], false, never>; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration, never>; } From 9754fff3c828cab8cefef89cb9ede8dba90e2f4a Mon Sep 17 00:00:00 2001 From: lskramarov Date: Wed, 1 Oct 2025 19:29:54 +0300 Subject: [PATCH 16/17] feat: added example + refactoring --- apps/docs/src/app/structure.ts | 3 +- .../components-dev/app-switcher/module.ts | 24 +- .../app-switcher/app-switcher-dropdown-app.ts | 4 +- .../components/app-switcher/app-switcher.html | 36 +- .../app-switcher/app-switcher.ru.md | 2 +- .../components/app-switcher/app-switcher.ts | 148 ++++--- .../kbq-app-switcher-list-item.ts | 4 +- .../app-switcher-overview-example.ts | 98 ++--- .../app-switcher-sites-example.ts | 370 ++++++++++++++++++ .../components/app-switcher/index.ts | 6 +- packages/docs-examples/example-module.ts | 14 + .../components/app-switcher.api.md | 80 ++-- 12 files changed, 598 insertions(+), 191 deletions(-) create mode 100644 packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts diff --git a/apps/docs/src/app/structure.ts b/apps/docs/src/app/structure.ts index 6559d03af..dd686fdf5 100644 --- a/apps/docs/src/app/structure.ts +++ b/apps/docs/src/app/structure.ts @@ -288,7 +288,8 @@ const structure: DocsStructure = makeStructure({ svgPreview: 'app-switcher', hasApi: true, apiId: 'app-switcher', - hasExamples: false + hasExamples: false, + isNew: expiresAt('2025-10-20') }, { id: DocsStructureItemId.AgGrid, diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index 7aa69fcf3..f284ea036 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -4,29 +4,19 @@ import { FormsModule } from '@angular/forms'; import { KbaAppSwitcherSite, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; import { KbqBadgeModule } from '@koobiq/components/badge'; import { KbqButtonModule } from '@koobiq/components/button'; -import { KbqComponentColors, KbqOptionModule } from '@koobiq/components/core'; +import { KbqOptionModule } from '@koobiq/components/core'; import { KbqDividerModule } from '@koobiq/components/divider'; import { KbqDropdownModule } from '@koobiq/components/dropdown'; -import { KbqIcon } from '@koobiq/components/icon'; import { AppSwitcherExamplesModule } from '../../docs-examples/components/app-switcher'; @Component({ standalone: true, - imports: [AppSwitcherExamplesModule, KbqButtonModule, KbqIcon, KbqAppSwitcherModule], + imports: [AppSwitcherExamplesModule, KbqButtonModule, KbqAppSwitcherModule], selector: 'dev-examples', template: ` - - - - - - - - - - - +
+ `, changeDetection: ChangeDetectionStrategy.OnPush }) @@ -483,8 +473,4 @@ class DevExamples { encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) -export class DevApp { - componentColors = KbqComponentColors; - - modelValue: any = ''; -} +export class DevApp {} diff --git a/packages/components/app-switcher/app-switcher-dropdown-app.ts b/packages/components/app-switcher/app-switcher-dropdown-app.ts index 123151e78..d570dfb1e 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-app.ts +++ b/packages/components/app-switcher/app-switcher-dropdown-app.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@a import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; -import { KbaAppSwitcherApp } from './app-switcher'; +import { KbqAppSwitcherApp } from './app-switcher'; @Component({ standalone: true, @@ -49,5 +49,5 @@ import { KbaAppSwitcherApp } from './app-switcher'; ] }) export class KbqAppSwitcherDropdownApp extends KbqDropdownItem { - @Input('kbq-app-switcher-dropdown-app') app: KbaAppSwitcherApp; + @Input('kbq-app-switcher-dropdown-app') app: KbqAppSwitcherApp; } diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index eae6db407..7c5869482 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -1,4 +1,4 @@ -@if (trigger.search) { +@if (trigger.withSearch) {
@@ -18,19 +18,21 @@
@if (!searchControl.getRawValue()) { -
-
-
{{ trigger.selectedSite?.name }}
- @if (trigger.selectedSite?.status) { - - {{ trigger.selectedSite.status }} - - } -
+
+ @if (trigger.sitesMode) { +
+
{{ trigger.selectedSite?.name }}
+ @if (trigger.selectedSite?.status) { + + {{ trigger.selectedSite.status }} + + } +
+ }
- @for (app of trigger.selectedSite?.apps; track app) { + @for (app of trigger.currentApps; track app) { @if (!app.aliases) {
} @else { -
+
} }
@for (alias of activeApp?.aliases; track alias) { -
+ }
} diff --git a/packages/components/app-switcher/app-switcher.ru.md b/packages/components/app-switcher/app-switcher.ru.md index 62d58f61d..be4f5d985 100644 --- a/packages/components/app-switcher/app-switcher.ru.md +++ b/packages/components/app-switcher/app-switcher.ru.md @@ -8,4 +8,4 @@ Поиск появляется, если доступно больше 7 приложений. Результаты представлены в виде плоского списка. - + diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index 7391f7b63..cbb571cbe 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -34,6 +34,7 @@ import { KbqPopUp, KbqPopUpTrigger, POSITION_TO_CSS_MAP, + PopUpPlacements, PopUpSizes, PopUpTriggers, applyPopupMargins @@ -52,13 +53,13 @@ import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; import { KbqAppSwitcherDropdownSite } from './app-switcher-dropdown-site'; import { KbqAppSwitcherListItem } from './kbq-app-switcher-list-item'; -export interface KbaAppSwitcherApp { +export interface KbqAppSwitcherApp { name: string; id: string | number; type?: string | number; icon?: string; caption?: string; - aliases?: KbaAppSwitcherApp[]; + aliases?: KbqAppSwitcherApp[]; link?: string; } @@ -67,24 +68,33 @@ export interface KbaAppSwitcherSite { id: string | number; status?: string; icon?: string; - apps: KbaAppSwitcherApp[]; + apps: KbqAppSwitcherApp[]; } -export function defaultGroupBy(groupedApps: Record>, app: KbaAppSwitcherApp) { - const appType = app.type ? app.type.toString() : 'untyped'; - - if (groupedApps[appType]) { - groupedApps[appType].aliases!.push(app); +export function defaultGroupBy( + app: KbqAppSwitcherApp, + groups: Record, + untyped: KbqAppSwitcherApp[] +) { + if (!app.type) { + untyped.push(app); } else { - groupedApps[appType] = { - name: appType, - aliases: [] - }; + const appType = app.type.toString(); + + if (groups[appType]) { + groups[appType].aliases!.push(app); + } else { + groups[appType] = { + name: appType, + aliases: [app], + id: '' + }; + } } } -export const MAX_APPS_FOR_ENABLE_SEARCH = 7; -export const MAX_APPS_FOR_ENABLE_GROUPING = 3; +export const MIN_APPS_FOR_ENABLE_SEARCH: number = 7; +export const MIN_APPS_FOR_ENABLE_GROUPING: number = 3; @Component({ standalone: true, @@ -124,7 +134,7 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { ); get withSites(): boolean { - return !!this.trigger.sites.length; + return this.trigger.sitesMode; } prefix = 'kbq-app-switcher'; @@ -134,7 +144,7 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { isTrapFocus: boolean = false; protected activeSite: KbaAppSwitcherSite; - protected activeApp: KbaAppSwitcherApp; + protected activeApp: KbqAppSwitcherApp; @ViewChild(KbqInput) input: KbqInput; @ViewChild('otherSites') otherSites: KbqDropdownTrigger; @@ -168,12 +178,12 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { this.hide(0); } - selectAppInSite(site, app: KbaAppSwitcherApp) { + selectAppInSite(site, app: KbqAppSwitcherApp) { this.trigger.selectedSite = site; this.trigger.selectedApp = app; - this.trigger.selectedSiteChanges.emit(site); - this.trigger.selectedAppChanges.emit(app); + this.trigger.selectedSiteChange.emit(site); + this.trigger.selectedAppChange.emit(app); } private filterSites(query: string | null): KbaAppSwitcherSite[] { @@ -235,14 +245,28 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple footer: string | TemplateRef; private closeOnScroll: null; - get search(): boolean { - return this.appsCount > MAX_APPS_FOR_ENABLE_SEARCH; + get withSearch(): boolean { + return this.appsCount > MIN_APPS_FOR_ENABLE_SEARCH; } get appsCount(): number { - return this.originalSites.reduce((acc, site) => acc + site.apps.length, 0); + if (this.sitesMode) { + return this.originalSites.reduce((acc, site) => acc + site.apps.length, 0); + } + + return this.originalApps.length; } + get sitesMode(): boolean { + return this.originalSites?.length > 0; + } + + get currentApps() { + return this.sitesMode ? this.selectedSite.apps : this._parsedApps; + } + + @Input('kbqAppSwitcherPlacement') placement: PopUpPlacements = PopUpPlacements.BottomLeft; + @Input() get sites(): KbaAppSwitcherSite[] { return this._parsedSites; @@ -256,29 +280,51 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple value.forEach((site: KbaAppSwitcherSite) => { const newSite: KbaAppSwitcherSite = { ...site, apps: [] }; - const groupedApps: Record = {}; + newSite.apps = this.makeGroupsForApps(site.apps, MIN_APPS_FOR_ENABLE_GROUPING); - // todo refactor - if (site.apps.length > MAX_APPS_FOR_ENABLE_GROUPING) { - site.apps.forEach((app) => { - this.groupBy(groupedApps, app); - }); + this._parsedSites.push(newSite); + }); + } - if (Object.values(groupedApps).length > 1) { - newSite.apps = Object.values(groupedApps).filter((app) => app.name !== 'untyped'); - } + private _parsedSites: KbaAppSwitcherSite[]; - if (groupedApps.untyped?.aliases?.length) { - newSite.apps.push(...groupedApps.untyped.aliases); - } - } + @Input() + get apps(): KbqAppSwitcherApp[] { + return this._parsedApps; + } - this._parsedSites.push(newSite); + set apps(apps: KbqAppSwitcherApp[]) { + this.originalApps = apps; + + this._parsedApps = this.makeGroupsForApps(this.originalApps, MIN_APPS_FOR_ENABLE_GROUPING); + } + + private makeGroupsForApps(apps: KbqAppSwitcherApp[], minAppsForGrouping: number): KbqAppSwitcherApp[] { + const groups: Record = {}; + const untyped: KbqAppSwitcherApp[] = []; + const groupedApps: KbqAppSwitcherApp[] = []; + + apps.forEach((app) => { + this.groupBy(app, groups, untyped); + }); + + Object.values(groups).forEach((group) => { + if (group.aliases && group.aliases.length > minAppsForGrouping) { + groupedApps.push(group); + } else { + untyped.push(...group.aliases!); + } }); + + groupedApps.push(...untyped); + + return groupedApps; } - private _parsedSites: KbaAppSwitcherSite[]; + private _parsedApps: KbqAppSwitcherApp[]; + originalSites: KbaAppSwitcherSite[]; + originalApps: KbqAppSwitcherApp[]; // Function to group the apps by type. The first argument is an object with app types. The second is an app @Input() @@ -286,7 +332,9 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple return this._groupBy; } - set groupBy(fn: (groupedApps: Record>, app: KbaAppSwitcherApp) => void) { + set groupBy( + fn: (app: KbqAppSwitcherApp, groups: Record, untyped: KbqAppSwitcherApp[]) => void + ) { if (typeof fn !== 'function') { throw new Error('The argument must be a function'); } @@ -304,29 +352,15 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple set selectedSite(value: KbaAppSwitcherSite) { const originValue = this.originalSites.find((site) => value.id === site.id) as KbaAppSwitcherSite; const newSite: KbaAppSwitcherSite = { ...originValue, apps: [] }; - const groupedApps: Record = {}; - - if (originValue.apps.length > MAX_APPS_FOR_ENABLE_GROUPING) { - originValue.apps.forEach((app) => { - this.groupBy(groupedApps, app); - }); - - if (Object.values(groupedApps).length > 1) { - newSite.apps = Object.values(groupedApps).filter((app) => app.name !== 'untyped'); - } - if (groupedApps.untyped?.aliases?.length) { - newSite.apps.push(...groupedApps.untyped.aliases); - } - } + newSite.apps = this.makeGroupsForApps(originValue.apps, MIN_APPS_FOR_ENABLE_GROUPING); this._parsedSelectedSite = newSite; } - private _parsedSelectedSite: KbaAppSwitcherSite; + @Input() selectedApp: KbqAppSwitcherApp; - @Input() apps: KbaAppSwitcherApp[]; - @Input() selectedApp: KbaAppSwitcherApp; + private _parsedSelectedSite: KbaAppSwitcherSite; @Input({ transform: booleanAttribute }) get disabled(): boolean { @@ -355,8 +389,8 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple @Output('kbqVisibleChange') readonly visibleChange = new EventEmitter(); - @Output() readonly selectedSiteChanges = new EventEmitter(); - @Output() readonly selectedAppChanges = new EventEmitter(); + @Output() readonly selectedSiteChange = new EventEmitter(); + @Output() readonly selectedAppChange = new EventEmitter(); protected originSelector = '.kbq-app-switcher'; diff --git a/packages/components/app-switcher/kbq-app-switcher-list-item.ts b/packages/components/app-switcher/kbq-app-switcher-list-item.ts index a419e6d80..46152a725 100644 --- a/packages/components/app-switcher/kbq-app-switcher-list-item.ts +++ b/packages/components/app-switcher/kbq-app-switcher-list-item.ts @@ -2,7 +2,7 @@ import { booleanAttribute, ChangeDetectionStrategy, Component, Input, ViewEncaps import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; -import { KbaAppSwitcherApp } from './app-switcher'; +import { KbqAppSwitcherApp } from './app-switcher'; @Component({ standalone: true, @@ -53,7 +53,7 @@ import { KbaAppSwitcherApp } from './app-switcher'; ] }) export class KbqAppSwitcherListItem extends KbqDropdownItem { - @Input() app: KbaAppSwitcherApp; + @Input() app: KbqAppSwitcherApp; @Input({ transform: booleanAttribute }) toggle = false; @Input() collapsed: boolean = false; diff --git a/packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts b/packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts index 93b90109e..cff4752e0 100644 --- a/packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts +++ b/packages/docs-examples/components/app-switcher/app-switcher-overview/app-switcher-overview-example.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; -import { KbaAppSwitcherSite, KbqAppSwitcherTrigger } from '@koobiq/components/app-switcher'; +import { KbqAppSwitcherApp, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; import { KbqAutocompleteModule } from '@koobiq/components/autocomplete'; import { KbqButtonModule } from '@koobiq/components/button'; import { KbqFormsModule } from '@koobiq/components/core'; @@ -21,74 +21,54 @@ import { KbqInputModule } from '@koobiq/components/input'; KbqInputModule, ReactiveFormsModule, KbqFormsModule, - KbqAppSwitcherTrigger, + KbqAppSwitcherModule, KbqButtonModule, KbqIcon ], template: ` - ` }) export class AppSwitcherOverviewExample { - sites: KbaAppSwitcherSite[] = [ + apps: KbqAppSwitcherApp[] = [ { - name: 'СЗФО', - id: '02', - apps: [ - { - name: 'Byte Sentinel', - caption: 'Byte 001', - id: 'SZFO_01', - icon: '', - type: '' - }, - { - name: 'CryptoWall', - id: 'SZFO_02', - type: '' - }, - { - name: 'App Instance 1', - caption: 'Instance Alias One', - id: 'SZFO_03', - type: '' - }, - { - name: 'App Instance 2', - id: 'SZFO_04', - type: '' - }, - { - name: 'App Instance 3', - caption: 'Instance Alias Three', - id: 'SZFO_05', - type: '' - }, - { - name: 'App Instance 4', - caption: 'Instance Alias Four', - id: 'SZFO_06', - type: '' - }, - { - name: 'Phantom Gate', - id: 'SZFO_07', - type: '' - }, - { - name: 'SentraLock', - id: 'SZFO_08', - caption: 'Lock-sentral-urals', - type: '' - }, - { - name: 'Zero Trace', - id: 'SZFO_09', - type: '' - } - ] + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'SZFO_01', + icon: '' + }, + { + name: 'CryptoWall', + id: 'SZFO_02' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'SZFO_03' + }, + { + name: 'App Instance 2', + id: 'SZFO_04' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'SZFO_05' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'SZFO_06' + }, + { + name: 'Phantom Gate', + id: 'SZFO_07' } ]; + selectedApp: KbqAppSwitcherApp = this.apps[0]; } diff --git a/packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts b/packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts new file mode 100644 index 000000000..42a1491b8 --- /dev/null +++ b/packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts @@ -0,0 +1,370 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { KbaAppSwitcherSite, KbqAppSwitcherApp, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; +import { KbqAutocompleteModule } from '@koobiq/components/autocomplete'; +import { KbqButtonModule } from '@koobiq/components/button'; +import { KbqFormsModule } from '@koobiq/components/core'; +import { KbqFormFieldModule } from '@koobiq/components/form-field'; +import { KbqIcon } from '@koobiq/components/icon'; +import { KbqInputModule } from '@koobiq/components/input'; + +/** + * @title app-switcher-sites + */ +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + selector: 'app-switcher-sites-example', + imports: [ + KbqFormFieldModule, + KbqAutocompleteModule, + KbqInputModule, + ReactiveFormsModule, + KbqFormsModule, + KbqAppSwitcherModule, + KbqButtonModule, + KbqIcon + ], + template: ` + App: {{ selectedApp.name }} +
+ Site: {{ selectedSite.name }} +
+
+ + ` +}) +export class AppSwitcherSitesExample { + sites: KbaAppSwitcherSite[] = [ + { + name: 'СЗФО', + id: '02', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'SZFO_01', + icon: '', + type: 'NAD' + }, + { + name: 'CryptoWall', + id: 'SZFO_02', + type: 'NAD' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'SZFO_03', + type: 'NAD' + }, + { + name: 'App Instance 2', + id: 'SZFO_04', + type: 'NAD' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'SZFO_05', + type: 'NE-NAD' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'SZFO_06', + type: 'NE-NAD' + }, + { + name: 'Phantom Gate', + id: 'SZFO_07', + type: 'NE-NAD' + }, + { + name: 'SentraLock', + id: 'SZFO_08', + caption: 'Lock-sentral-urals', + type: 'NE-NAD' + }, + { + name: 'Zero Trace', + id: 'SZFO_09', + type: '' + } + ] + }, + { + name: 'ЮФО', + id: '03', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'UFO_01', + type: 'SIEM' + }, + { + name: 'CryptoWall', + id: 'UFO_02', + type: 'SIEM' + }, + { + name: 'Phantom Gate', + id: 'UFO_03', + type: 'SIEM' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals', + id: 'UFO_04', + type: 'SIEM' + }, + { + name: 'Zero Trace', + id: 'UFO_05', + type: '' + } + ] + }, + { + name: 'Южный Суверенный Федеральный Округ ФО', + status: 'Главная', + id: '04', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'USFO_01', + type: 'APP' + }, + { + name: 'CryptoWall', + id: 'USFO_02', + type: 'APP' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'USFO_03', + type: 'APP' + }, + { + name: 'App Instance 2', + id: 'USFO_04', + type: 'APP' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'USFO_05', + type: 'APP' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'USFO_06', + type: 'APP' + }, + { + name: 'Phantom Gate', + id: 'USFO_07', + type: '' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals', + id: 'USFO_08', + type: '' + }, + { + name: 'Zero Trace', + id: 'USFO_09', + type: '' + } + ] + }, + { + name: 'ПФО', + id: '05', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'PFO_01', + type: '' + }, + { + name: 'CryptoWall', + id: 'PFO_02', + type: '' + }, + { + name: 'Phantom Gate', + id: 'PFO_03', + type: '' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals', + id: 'PFO_04', + type: '' + }, + { + name: 'Zero Trace', + id: 'PFO_05', + type: '' + } + ] + }, + { + name: 'УФО', + id: '06', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'UFO_01', + type: '' + }, + { + name: 'CryptoWall', + id: 'UFO_02', + type: '' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'UFO_03', + type: '' + }, + { + name: 'App Instance 2', + id: 'UFO_04', + type: '' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'UFO_05', + type: '' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'UFO_06', + type: '' + }, + { + name: 'Phantom Gate', + id: 'UFO_07', + type: '' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals', + id: 'UFO_08', + type: '' + }, + { + name: 'Zero Trace', + id: 'UFO_09', + type: '' + } + ] + }, + { + name: 'СФО', + id: '07', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'SFO_01', + type: '' + }, + { + name: 'CryptoWall', + id: 'SFO_02', + type: '' + }, + { + name: 'Phantom Gate', + id: 'SFO_03', + type: '' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals', + id: 'SFO_04', + type: '' + }, + { + name: 'Zero Trace', + id: 'SFO_05', + type: '' + } + ] + }, + { + name: 'ДФО', + id: '08', + apps: [ + { + name: 'Byte Sentinel', + caption: 'Byte 001', + id: 'DFO_01', + type: '' + }, + { + name: 'CryptoWall', + id: 'DFO_02', + type: '' + }, + { + name: 'App Instance 1', + caption: 'Instance Alias One', + id: 'DFO_03', + type: '' + }, + { + name: 'App Instance 2', + id: 'DFO_04', + type: '' + }, + { + name: 'App Instance 3', + caption: 'Instance Alias Three', + id: 'DFO_05', + type: '' + }, + { + name: 'App Instance 4', + caption: 'Instance Alias Four', + id: 'DFO_06', + type: '' + }, + { + name: 'Phantom Gate', + id: 'DFO_07', + type: '' + }, + { + name: 'SentraLock', + caption: 'Lock-sentral-urals', + id: 'DFO_08', + type: '' + }, + { + name: 'Zero Trace', + id: 'DFO_09', + type: '' + } + ] + } + ]; + + selectedSite: KbaAppSwitcherSite = this.sites[0]; + selectedApp: KbqAppSwitcherApp = this.sites[0].apps[0]; +} diff --git a/packages/docs-examples/components/app-switcher/index.ts b/packages/docs-examples/components/app-switcher/index.ts index 3aedf311b..2242b0abf 100644 --- a/packages/docs-examples/components/app-switcher/index.ts +++ b/packages/docs-examples/components/app-switcher/index.ts @@ -1,10 +1,12 @@ import { NgModule } from '@angular/core'; import { AppSwitcherOverviewExample } from './app-switcher-overview/app-switcher-overview-example'; +import { AppSwitcherSitesExample } from './app-switcher-sites/app-switcher-sites-example'; -export { AppSwitcherOverviewExample }; +export { AppSwitcherOverviewExample, AppSwitcherSitesExample }; const EXAMPLES = [ - AppSwitcherOverviewExample + AppSwitcherOverviewExample, + AppSwitcherSitesExample ]; @NgModule({ diff --git a/packages/docs-examples/example-module.ts b/packages/docs-examples/example-module.ts index fa28633bf..56a3340b6 100644 --- a/packages/docs-examples/example-module.ts +++ b/packages/docs-examples/example-module.ts @@ -300,6 +300,18 @@ export const EXAMPLE_COMPONENTS: {[id: string]: LiveExample} = { "primaryFile": "app-switcher-overview-example.ts", "importPath": "components/app-switcher" }, + "app-switcher-sites": { + "packagePath": "components/app-switcher/app-switcher-sites", + "title": "app-switcher-sites", + "componentName": "AppSwitcherSitesExample", + "files": [ + "app-switcher-sites-example.ts" + ], + "selector": "app-switcher-sites-example", + "additionalComponents": [], + "primaryFile": "app-switcher-sites-example.ts", + "importPath": "components/app-switcher" + }, "autocomplete-overview": { "packagePath": "components/autocomplete/autocomplete-overview", "title": "Autocomplete", @@ -5292,6 +5304,8 @@ return import('@koobiq/docs-examples/components/alert'); case 'alert-variants': return import('@koobiq/docs-examples/components/alert'); case 'app-switcher-overview': +return import('@koobiq/docs-examples/components/app-switcher'); + case 'app-switcher-sites': return import('@koobiq/docs-examples/components/app-switcher'); case 'autocomplete-overview': return import('@koobiq/docs-examples/components/autocomplete'); diff --git a/tools/public_api_guard/components/app-switcher.api.md b/tools/public_api_guard/components/app-switcher.api.md index 5a17d81ba..1dc5356aa 100644 --- a/tools/public_api_guard/components/app-switcher.api.md +++ b/tools/public_api_guard/components/app-switcher.api.md @@ -30,6 +30,7 @@ import { Observable } from 'rxjs'; import { OnInit } from '@angular/core'; import { Overlay } from '@angular/cdk/overlay'; import { OverlayConfig } from '@angular/cdk/overlay'; +import { PopUpPlacements } from '@koobiq/components/core'; import { PopUpSizes } from '@koobiq/components/core'; import { ScrollStrategy } from '@angular/cdk/overlay'; import { Subscription } from 'rxjs'; @@ -37,30 +38,12 @@ import { TemplateRef } from '@angular/core'; import { Type } from '@angular/core'; // @public (undocumented) -export function defaultGroupBy(groupedApps: Record>, app: KbaAppSwitcherApp): void; - -// @public (undocumented) -export interface KbaAppSwitcherApp { - // (undocumented) - aliases?: KbaAppSwitcherApp[]; - // (undocumented) - caption?: string; - // (undocumented) - icon?: string; - // (undocumented) - id: string | number; - // (undocumented) - link?: string; - // (undocumented) - name: string; - // (undocumented) - type?: string | number; -} +export function defaultGroupBy(app: KbqAppSwitcherApp, groups: Record, untyped: KbqAppSwitcherApp[]): void; // @public (undocumented) export interface KbaAppSwitcherSite { // (undocumented) - apps: KbaAppSwitcherApp[]; + apps: KbqAppSwitcherApp[]; // (undocumented) icon?: string; // (undocumented) @@ -84,7 +67,7 @@ export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER: { // @public (undocumented) export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { // (undocumented) - protected activeApp: KbaAppSwitcherApp; + protected activeApp: KbqAppSwitcherApp; // (undocumented) protected activeSite: KbaAppSwitcherSite; // (undocumented) @@ -104,7 +87,7 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { // (undocumented) readonly searchControl: FormControl; // (undocumented) - selectAppInSite(site: any, app: KbaAppSwitcherApp): void; + selectAppInSite(site: any, app: KbqAppSwitcherApp): void; // (undocumented) trigger: KbqAppSwitcherTrigger; // (undocumented) @@ -124,10 +107,28 @@ export const kbqAppSwitcherAnimations: { readonly state: AnimationTriggerMetadata; }; +// @public (undocumented) +export interface KbqAppSwitcherApp { + // (undocumented) + aliases?: KbqAppSwitcherApp[]; + // (undocumented) + caption?: string; + // (undocumented) + icon?: string; + // (undocumented) + id: string | number; + // (undocumented) + link?: string; + // (undocumented) + name: string; + // (undocumented) + type?: string | number; +} + // @public (undocumented) export class KbqAppSwitcherDropdownApp extends KbqDropdownItem { // (undocumented) - app: KbaAppSwitcherApp; + app: KbqAppSwitcherApp; // (undocumented) static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) @@ -147,7 +148,7 @@ export class KbqAppSwitcherDropdownSite extends KbqDropdownItem { // @public (undocumented) export class KbqAppSwitcherListItem extends KbqDropdownItem { // (undocumented) - app: KbaAppSwitcherApp; + app: KbqAppSwitcherApp; // (undocumented) clickHandler(event: MouseEvent): void; // (undocumented) @@ -183,7 +184,8 @@ export function kbqAppSwitcherScrollStrategyFactory(overlay: Overlay): () => Scr // @public (undocumented) export class KbqAppSwitcherTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { // (undocumented) - apps: KbaAppSwitcherApp[]; + get apps(): KbqAppSwitcherApp[]; + set apps(apps: KbqAppSwitcherApp[]); // (undocumented) get appsCount(): number; // (undocumented) @@ -195,6 +197,8 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple // (undocumented) content: string | TemplateRef; // (undocumented) + get currentApps(): KbqAppSwitcherApp[]; + // (undocumented) customClass: string; // (undocumented) get disabled(): boolean; @@ -204,8 +208,8 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple // (undocumented) getOverlayHandleComponentType(): Type; // (undocumented) - get groupBy(): (groupedApps: Record>, app: KbaAppSwitcherApp) => void; - set groupBy(fn: (groupedApps: Record>, app: KbaAppSwitcherApp) => void); + get groupBy(): (app: KbqAppSwitcherApp, groups: Record, untyped: KbqAppSwitcherApp[]) => void; + set groupBy(fn: (app: KbqAppSwitcherApp, groups: Record, untyped: KbqAppSwitcherApp[]) => void); // (undocumented) get hasClickTrigger(): boolean; // (undocumented) @@ -221,32 +225,36 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple // (undocumented) offset: number | null; // (undocumented) + originalApps: KbqAppSwitcherApp[]; + // (undocumented) originalSites: KbaAppSwitcherSite[]; // (undocumented) protected originSelector: string; // (undocumented) protected get overlayConfig(): OverlayConfig; // (undocumented) + placement: PopUpPlacements; + // (undocumented) readonly placementChange: EventEmitter; // (undocumented) protected preventClosingByInnerScrollSubscription: Subscription; // (undocumented) protected scrollStrategy: () => ScrollStrategy; // (undocumented) - get search(): boolean; - // (undocumented) - selectedApp: KbaAppSwitcherApp; + selectedApp: KbqAppSwitcherApp; // (undocumented) - readonly selectedAppChanges: EventEmitter; + readonly selectedAppChange: EventEmitter; // (undocumented) get selectedSite(): KbaAppSwitcherSite; set selectedSite(value: KbaAppSwitcherSite); // (undocumented) - readonly selectedSiteChanges: EventEmitter; + readonly selectedSiteChange: EventEmitter; // (undocumented) get sites(): KbaAppSwitcherSite[]; set sites(value: KbaAppSwitcherSite[]); // (undocumented) + get sitesMode(): boolean; + // (undocumented) trigger: string; // (undocumented) updateClassMap(newPlacement?: string): void; @@ -256,16 +264,18 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple // (undocumented) readonly visibleChange: EventEmitter; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + get withSearch(): boolean; + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } // @public (undocumented) -export const MAX_APPS_FOR_ENABLE_GROUPING = 3; +export const MIN_APPS_FOR_ENABLE_GROUPING: number; // @public (undocumented) -export const MAX_APPS_FOR_ENABLE_SEARCH = 7; +export const MIN_APPS_FOR_ENABLE_SEARCH: number; // (No @packageDocumentation comment for this package) From ec330b804c875cac6f7e289ffb24012a83f211b6 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Thu, 2 Oct 2025 10:49:21 +0300 Subject: [PATCH 17/17] feat: api + refactoring --- .../components-dev/app-switcher/module.ts | 4 +- .../app-switcher/app-switcher-animations.ts | 1 + .../app-switcher/app-switcher-dropdown-app.ts | 1 + .../app-switcher-dropdown-site.ts | 5 +- .../components/app-switcher/app-switcher.html | 4 +- .../app-switcher/app-switcher.module.ts | 32 +-- .../components/app-switcher/app-switcher.ts | 194 +++++++++++------- .../app-switcher/examples.app-switcher.en.md | 5 + .../app-switcher/examples.app-switcher.ru.md | 5 + .../kbq-app-switcher-list-item.ts | 4 +- .../components/core/pop-up/pop-up-trigger.ts | 4 +- .../navbar/navbar-item.component.ts | 4 +- .../popover/popover-confirm.component.ts | 4 +- .../components/popover/popover.component.ts | 4 +- .../components/tooltip/tooltip.component.ts | 12 +- .../app-switcher-sites-example.ts | 10 +- .../components/app-switcher.api.md | 174 ++++++---------- tools/public_api_guard/components/core.api.md | 8 +- 18 files changed, 231 insertions(+), 244 deletions(-) diff --git a/packages/components-dev/app-switcher/module.ts b/packages/components-dev/app-switcher/module.ts index f284ea036..2b540945c 100644 --- a/packages/components-dev/app-switcher/module.ts +++ b/packages/components-dev/app-switcher/module.ts @@ -1,7 +1,7 @@ import { A11yModule } from '@angular/cdk/a11y'; import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { KbaAppSwitcherSite, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; +import { KbqAppSwitcherModule, KbqAppSwitcherSite } from '@koobiq/components/app-switcher'; import { KbqBadgeModule } from '@koobiq/components/badge'; import { KbqButtonModule } from '@koobiq/components/button'; import { KbqOptionModule } from '@koobiq/components/core'; @@ -21,7 +21,7 @@ import { AppSwitcherExamplesModule } from '../../docs-examples/components/app-sw changeDetection: ChangeDetectionStrategy.OnPush }) class DevExamples { - sites: KbaAppSwitcherSite[] = [ + sites: KbqAppSwitcherSite[] = [ { name: 'ЦФО', id: '01', diff --git a/packages/components/app-switcher/app-switcher-animations.ts b/packages/components/app-switcher/app-switcher-animations.ts index ce382f774..cad3ecfc3 100644 --- a/packages/components/app-switcher/app-switcher-animations.ts +++ b/packages/components/app-switcher/app-switcher-animations.ts @@ -1,5 +1,6 @@ import { animate, AnimationTriggerMetadata, state, style, transition, trigger } from '@angular/animations'; +/** @docs-private */ export const kbqAppSwitcherAnimations: { readonly state: AnimationTriggerMetadata; } = { diff --git a/packages/components/app-switcher/app-switcher-dropdown-app.ts b/packages/components/app-switcher/app-switcher-dropdown-app.ts index d570dfb1e..53d03b5f4 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-app.ts +++ b/packages/components/app-switcher/app-switcher-dropdown-app.ts @@ -4,6 +4,7 @@ import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; import { KbqAppSwitcherApp } from './app-switcher'; +/** @docs-private */ @Component({ standalone: true, selector: '[kbq-app-switcher-dropdown-app]', diff --git a/packages/components/app-switcher/app-switcher-dropdown-site.ts b/packages/components/app-switcher/app-switcher-dropdown-site.ts index a5407464f..1bc470904 100644 --- a/packages/components/app-switcher/app-switcher-dropdown-site.ts +++ b/packages/components/app-switcher/app-switcher-dropdown-site.ts @@ -3,8 +3,9 @@ import { KbqBadgeModule } from '@koobiq/components/badge'; import { KBQ_TITLE_TEXT_REF } from '@koobiq/components/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; -import { KbaAppSwitcherSite } from './app-switcher'; +import { KbqAppSwitcherSite } from './app-switcher'; +/** @docs-private */ @Component({ standalone: true, selector: '[kbq-app-switcher-dropdown-site]', @@ -41,5 +42,5 @@ import { KbaAppSwitcherSite } from './app-switcher'; ] }) export class KbqAppSwitcherDropdownSite extends KbqDropdownItem { - @Input('kbq-app-switcher-dropdown-site') site: KbaAppSwitcherSite; + @Input('kbq-app-switcher-dropdown-site') site: KbqAppSwitcherSite; } diff --git a/packages/components/app-switcher/app-switcher.html b/packages/components/app-switcher/app-switcher.html index 7c5869482..3d76e6833 100644 --- a/packages/components/app-switcher/app-switcher.html +++ b/packages/components/app-switcher/app-switcher.html @@ -7,7 +7,7 @@ kbqInput placeholder="Поиск" [formControl]="searchControl" - (keydown.escape)="onEscape()" + (keydown.escape)="escapeHandler()" /> @@ -66,7 +66,7 @@
- @if (withSites) { + @if (trigger.sitesMode) {
diff --git a/packages/components/app-switcher/app-switcher.module.ts b/packages/components/app-switcher/app-switcher.module.ts index 9db696366..a6a439e0a 100644 --- a/packages/components/app-switcher/app-switcher.module.ts +++ b/packages/components/app-switcher/app-switcher.module.ts @@ -1,22 +1,9 @@ -import { - A11yModule, - ConfigurableFocusTrapFactory, - FOCUS_TRAP_INERT_STRATEGY, - FocusTrapFactory -} from '@angular/cdk/a11y'; -import { CdkObserveContent } from '@angular/cdk/observers'; -import { OverlayModule } from '@angular/cdk/overlay'; -import { NgClass, NgTemplateOutlet } from '@angular/common'; +import { ConfigurableFocusTrapFactory, FOCUS_TRAP_INERT_STRATEGY, FocusTrapFactory } from '@angular/cdk/a11y'; import { NgModule } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { KbqButtonModule } from '@koobiq/components/button'; import { EmptyFocusTrapStrategy } from '@koobiq/components/core'; -import { KbqFormField } from '@koobiq/components/form-field'; -import { KbqIconModule } from '@koobiq/components/icon'; -import { KbqInputModule } from '@koobiq/components/input'; import { KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER, - KbqAppSwitcher, + KbqAppSwitcherComponent, KbqAppSwitcherTrigger } from './app-switcher'; import { KbqAppSwitcherDropdownApp } from './app-switcher-dropdown-app'; @@ -25,24 +12,13 @@ import { KbqAppSwitcherListItem } from './kbq-app-switcher-list-item'; @NgModule({ imports: [ - KbqAppSwitcher, + KbqAppSwitcherComponent, KbqAppSwitcherTrigger, KbqAppSwitcherListItem, KbqAppSwitcherDropdownApp, - KbqAppSwitcherDropdownSite, - OverlayModule, - KbqButtonModule, - A11yModule, - KbqIconModule, - CdkObserveContent, - NgClass, - NgTemplateOutlet, - KbqFormField, - KbqInputModule, - FormsModule + KbqAppSwitcherDropdownSite ], exports: [ - KbqAppSwitcher, KbqAppSwitcherTrigger ], providers: [ diff --git a/packages/components/app-switcher/app-switcher.ts b/packages/components/app-switcher/app-switcher.ts index cbb571cbe..8a82a6942 100644 --- a/packages/components/app-switcher/app-switcher.ts +++ b/packages/components/app-switcher/app-switcher.ts @@ -42,7 +42,7 @@ import { import { KbqDividerModule } from '@koobiq/components/divider'; import { KbqDropdownModule, KbqDropdownTrigger } from '@koobiq/components/dropdown'; import { KbqFormFieldModule } from '@koobiq/components/form-field'; -import { KbqIcon } from '@koobiq/components/icon'; +import { KbqIconModule } from '@koobiq/components/icon'; import { KbqInput, KbqInputModule } from '@koobiq/components/input'; import { defaultOffsetYWithArrow } from '@koobiq/components/popover'; import { KbqScrollbarModule } from '@koobiq/components/scrollbar'; @@ -63,7 +63,7 @@ export interface KbqAppSwitcherApp { link?: string; } -export interface KbaAppSwitcherSite { +export interface KbqAppSwitcherSite { name: string; id: string | number; status?: string; @@ -71,6 +71,7 @@ export interface KbaAppSwitcherSite { apps: KbqAppSwitcherApp[]; } +/** @docs-private */ export function defaultGroupBy( app: KbqAppSwitcherApp, groups: Record, @@ -93,9 +94,27 @@ export function defaultGroupBy( } } -export const MIN_APPS_FOR_ENABLE_SEARCH: number = 7; -export const MIN_APPS_FOR_ENABLE_GROUPING: number = 3; +export const MIN_NUMBER_OF_APPS_TO_ENABLE_SEARCH: number = 7; +export const MIN_NUMBER_OF_APPS_TO_ENABLE_GROUPING: number = 3; +/** @docs-private */ +export const KBQ_APP_SWITCHER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( + 'kbq-app-switcher-scroll-strategy' +); + +/** @docs-private */ +export function kbqAppSwitcherScrollStrategyFactory(overlay: Overlay): () => ScrollStrategy { + return () => overlay.scrollStrategies.reposition({ scrollThrottle: 20 }); +} + +/** @docs-private */ +export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER = { + provide: KBQ_APP_SWITCHER_SCROLL_STRATEGY, + deps: [Overlay], + useFactory: kbqAppSwitcherScrollStrategyFactory +}; + +/** @docs-private */ @Component({ standalone: true, selector: 'kbq-app-switcher', @@ -108,45 +127,50 @@ export const MIN_APPS_FOR_ENABLE_GROUPING: number = 3; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [ + AsyncPipe, + FormsModule, + ReactiveFormsModule, KbqFormFieldModule, KbqInputModule, - FormsModule, + KbqIconModule, KbqDividerModule, KbqBadgeModule, - KbqIcon, KbqDropdownModule, - KbqAppSwitcherDropdownApp, - KbqAppSwitcherDropdownSite, - KbqAppSwitcherListItem, KbqScrollbarModule, KbqOptionModule, - ReactiveFormsModule, - AsyncPipe + KbqAppSwitcherDropdownApp, + KbqAppSwitcherDropdownSite, + KbqAppSwitcherListItem ], animations: [kbqAppSwitcherAnimations.state] }) -export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { +export class KbqAppSwitcherComponent extends KbqPopUp implements AfterViewInit { + /** @docs-private */ readonly searchControl = new FormControl(''); + /** @docs-private */ readonly filteredSites = this.searchControl.valueChanges.pipe( startWith(''), map((query) => this.filterSites(query)) ); - get withSites(): boolean { - return this.trigger.sitesMode; - } - + /** @docs-private */ prefix = 'kbq-app-switcher'; + /** @docs-private */ trigger: KbqAppSwitcherTrigger; + /** @docs-private */ isTrapFocus: boolean = false; - protected activeSite: KbaAppSwitcherSite; + /** @docs-private */ + protected activeSite: KbqAppSwitcherSite; + /** @docs-private */ protected activeApp: KbqAppSwitcherApp; + /** @docs-private */ @ViewChild(KbqInput) input: KbqInput; + /** @docs-private */ @ViewChild('otherSites') otherSites: KbqDropdownTrigger; ngAfterViewInit() { @@ -166,19 +190,23 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { }); } + /** @docs-private */ updateClassMap(placement: string, customClass: string, size: PopUpSizes) { super.updateClassMap(placement, customClass, { [`${this.prefix}_${size}`]: !!size }); } + /** @docs-private */ updateTrapFocus(isTrapFocus: boolean): void { this.isTrapFocus = isTrapFocus; } - onEscape() { + /** @docs-private */ + escapeHandler() { this.hide(0); } - selectAppInSite(site, app: KbqAppSwitcherApp) { + /** @docs-private */ + selectAppInSite(site: KbqAppSwitcherSite, app: KbqAppSwitcherApp) { this.trigger.selectedSite = site; this.trigger.selectedApp = app; @@ -186,7 +214,7 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { this.trigger.selectedAppChange.emit(app); } - private filterSites(query: string | null): KbaAppSwitcherSite[] { + private filterSites(query: string | null): KbqAppSwitcherSite[] { const filteredSites = structuredClone(this.trigger.originalSites); return query @@ -205,22 +233,6 @@ export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { } } -export const KBQ_APP_SWITCHER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( - 'kbq-app-switcher-scroll-strategy' -); - -// @docs-private -export function kbqAppSwitcherScrollStrategyFactory(overlay: Overlay): () => ScrollStrategy { - return () => overlay.scrollStrategies.reposition({ scrollThrottle: 20 }); -} - -// @docs-private -export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER = { - provide: KBQ_APP_SWITCHER_SCROLL_STRATEGY, - deps: [Overlay], - useFactory: kbqAppSwitcherScrollStrategyFactory -}; - @Directive({ standalone: true, selector: '[kbqAppSwitcher]', @@ -228,27 +240,42 @@ export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER = { host: { '[class.kbq-app-switcher_open]': 'isOpen', '[class.kbq-active]': 'hasClickTrigger && isOpen', - '(keydown)': 'handleKeydown($event)', - '(touchend)': 'handleTouchend()' + '(keydown)': 'keydownHandler($event)', + '(touchend)': 'touchendHandler()' } }) -export class KbqAppSwitcherTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { +export class KbqAppSwitcherTrigger + extends KbqPopUpTrigger + implements AfterContentInit, OnInit +{ + /** @docs-private */ protected scrollStrategy: () => ScrollStrategy = inject(KBQ_APP_SWITCHER_SCROLL_STRATEGY); // not used + /** @docs-private */ arrow: boolean = false; + /** @docs-private */ customClass: string; + /** @docs-private */ private hasBackdrop: boolean = false; + /** @docs-private */ private size: PopUpSizes = PopUpSizes.Medium; + /** @docs-private */ content: string | TemplateRef; + /** @docs-private */ header: string | TemplateRef; + /** @docs-private */ footer: string | TemplateRef; + /** @docs-private */ private closeOnScroll: null; + /** Whether search is used or not */ get withSearch(): boolean { - return this.appsCount > MIN_APPS_FOR_ENABLE_SEARCH; + return this.appsCount > MIN_NUMBER_OF_APPS_TO_ENABLE_SEARCH; } + /** Number of applications to choose from + * @docs-private */ get appsCount(): number { if (this.sitesMode) { return this.originalSites.reduce((acc, site) => acc + site.apps.length, 0); @@ -257,37 +284,53 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple return this.originalApps.length; } + /** Whether the sites are used or not + * @docs-private */ get sitesMode(): boolean { return this.originalSites?.length > 0; } + /** Whether the sites are used or not + * @docs-private */ get currentApps() { return this.sitesMode ? this.selectedSite.apps : this._parsedApps; } + /** Selected application */ + @Input() selectedApp: KbqAppSwitcherApp; + + /** Placement of popUp */ @Input('kbqAppSwitcherPlacement') placement: PopUpPlacements = PopUpPlacements.BottomLeft; + /** Class that will be used in the background */ + @Input() backdropClass: string = 'cdk-overlay-transparent-backdrop'; + + /** Offset of popUp */ + @Input({ transform: numberAttribute }) offset: number | null = defaultOffsetYWithArrow; + + /** Array of sites */ @Input() - get sites(): KbaAppSwitcherSite[] { + get sites(): KbqAppSwitcherSite[] { return this._parsedSites; } - set sites(value: KbaAppSwitcherSite[]) { + set sites(value: KbqAppSwitcherSite[]) { this.originalSites = value; this._parsedSites = []; - value.forEach((site: KbaAppSwitcherSite) => { - const newSite: KbaAppSwitcherSite = { ...site, apps: [] }; + value.forEach((site: KbqAppSwitcherSite) => { + const newSite: KbqAppSwitcherSite = { ...site, apps: [] }; - newSite.apps = this.makeGroupsForApps(site.apps, MIN_APPS_FOR_ENABLE_GROUPING); + newSite.apps = this.makeGroupsForApps(site.apps, MIN_NUMBER_OF_APPS_TO_ENABLE_GROUPING); this._parsedSites.push(newSite); }); } - private _parsedSites: KbaAppSwitcherSite[]; + private _parsedSites: KbqAppSwitcherSite[]; + /** Array of applications */ @Input() get apps(): KbqAppSwitcherApp[] { return this._parsedApps; @@ -296,7 +339,7 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple set apps(apps: KbqAppSwitcherApp[]) { this.originalApps = apps; - this._parsedApps = this.makeGroupsForApps(this.originalApps, MIN_APPS_FOR_ENABLE_GROUPING); + this._parsedApps = this.makeGroupsForApps(this.originalApps, MIN_NUMBER_OF_APPS_TO_ENABLE_GROUPING); } private makeGroupsForApps(apps: KbqAppSwitcherApp[], minAppsForGrouping: number): KbqAppSwitcherApp[] { @@ -323,10 +366,8 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple private _parsedApps: KbqAppSwitcherApp[]; - originalSites: KbaAppSwitcherSite[]; - originalApps: KbqAppSwitcherApp[]; - - // Function to group the apps by type. The first argument is an object with app types. The second is an app + /** Function to group the apps by type. The first argument is an app object with type. + * The second is a groups object and third is an array for untyped apps */ @Input() get groupBy() { return this._groupBy; @@ -344,24 +385,24 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple private _groupBy = defaultGroupBy; + /** Selected site */ @Input() - get selectedSite(): KbaAppSwitcherSite { + get selectedSite(): KbqAppSwitcherSite { return this._parsedSelectedSite; } - set selectedSite(value: KbaAppSwitcherSite) { - const originValue = this.originalSites.find((site) => value.id === site.id) as KbaAppSwitcherSite; - const newSite: KbaAppSwitcherSite = { ...originValue, apps: [] }; + set selectedSite(value: KbqAppSwitcherSite) { + const originValue = this.originalSites.find((site) => value.id === site.id) as KbqAppSwitcherSite; + const newSite: KbqAppSwitcherSite = { ...originValue, apps: [] }; - newSite.apps = this.makeGroupsForApps(originValue.apps, MIN_APPS_FOR_ENABLE_GROUPING); + newSite.apps = this.makeGroupsForApps(originValue.apps, MIN_NUMBER_OF_APPS_TO_ENABLE_GROUPING); this._parsedSelectedSite = newSite; } - @Input() selectedApp: KbqAppSwitcherApp; - - private _parsedSelectedSite: KbaAppSwitcherSite; + private _parsedSelectedSite: KbqAppSwitcherSite; + /** Whether the trigger is disabled. */ @Input({ transform: booleanAttribute }) get disabled(): boolean { return this._disabled; @@ -375,25 +416,34 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple } } - trigger: string = `${PopUpTriggers.Click}, ${PopUpTriggers.Keydown}`; - + /** @docs-private */ get hasClickTrigger(): boolean { return this.trigger.includes(PopUpTriggers.Click); } - @Input() backdropClass: string = 'cdk-overlay-transparent-backdrop'; - - @Input({ transform: numberAttribute }) offset: number | null = defaultOffsetYWithArrow; - + /** Emits a change event whenever the placement state changes. */ @Output('kbqPlacementChange') readonly placementChange = new EventEmitter(); + /** Emits a change event whenever the visible state changes. */ @Output('kbqVisibleChange') readonly visibleChange = new EventEmitter(); - @Output() readonly selectedSiteChange = new EventEmitter(); + /** @docs-private */ + @Output() readonly selectedSiteChange = new EventEmitter(); + /** @docs-private */ @Output() readonly selectedAppChange = new EventEmitter(); + /** @docs-private */ + trigger: string = `${PopUpTriggers.Click}, ${PopUpTriggers.Keydown}`; + + /** @docs-private */ + originalSites: KbqAppSwitcherSite[]; + /** @docs-private */ + originalApps: KbqAppSwitcherApp[]; + + /** @docs-private */ protected originSelector = '.kbq-app-switcher'; + /** @docs-private */ protected get overlayConfig(): OverlayConfig { return { panelClass: 'kbq-app-switcher__panel', @@ -402,6 +452,7 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple }; } + /** @docs-private */ protected preventClosingByInnerScrollSubscription: Subscription; ngOnInit(): void { @@ -443,6 +494,7 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple }); } + /** @docs-private */ updateData() { if (!this.instance) return; @@ -459,7 +511,8 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple } } - /** Updates the current position. */ + /** Updates the current position. + * @docs-private */ updatePosition(reapplyPosition: boolean = false) { this.overlayRef = this.createOverlay(); @@ -472,10 +525,12 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple } } - getOverlayHandleComponentType(): Type { - return KbqAppSwitcher; + /** @docs-private */ + getOverlayHandleComponentType(): Type { + return KbqAppSwitcherComponent; } + /** @docs-private */ updateClassMap(newPlacement: string = this.placement) { if (!this.instance) return; @@ -483,6 +538,7 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple this.instance.markForCheck(); } + /** @docs-private */ closingActions() { return merge( this.overlayRef!.outsidePointerEvents(), diff --git a/packages/components/app-switcher/examples.app-switcher.en.md b/packages/components/app-switcher/examples.app-switcher.en.md index e69de29bb..2faef3848 100644 --- a/packages/components/app-switcher/examples.app-switcher.en.md +++ b/packages/components/app-switcher/examples.app-switcher.en.md @@ -0,0 +1,5 @@ +🚧 **Documentation in progress** 🚧 + +Unfortunately, the documentation for this section is not ready yet. We are actively working on its creation and plan to add it soon. + +If you would like to contribute to the documentation or have any questions, please feel free to [open an issue](https://github.com/koobiq/angular-components/issues) in our GitHub repository. diff --git a/packages/components/app-switcher/examples.app-switcher.ru.md b/packages/components/app-switcher/examples.app-switcher.ru.md index e69de29bb..b7a203c6a 100644 --- a/packages/components/app-switcher/examples.app-switcher.ru.md +++ b/packages/components/app-switcher/examples.app-switcher.ru.md @@ -0,0 +1,5 @@ +🚧 **Документация в процессе написания** 🚧 + +К сожалению, документация для этого раздела еще не готова. Мы активно работаем над ее созданием и планируем добавить в ближайшее время. + +Если вы хотите помочь в написании документации или у вас есть вопросы, пожалуйста, [создайте issue](https://github.com/koobiq/angular-components/issues) в нашем репозитории на GitHub. diff --git a/packages/components/app-switcher/kbq-app-switcher-list-item.ts b/packages/components/app-switcher/kbq-app-switcher-list-item.ts index 46152a725..defdff926 100644 --- a/packages/components/app-switcher/kbq-app-switcher-list-item.ts +++ b/packages/components/app-switcher/kbq-app-switcher-list-item.ts @@ -4,6 +4,7 @@ import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqIcon } from '@koobiq/components/icon'; import { KbqAppSwitcherApp } from './app-switcher'; +/** @docs-private */ @Component({ standalone: true, selector: '[kbq-app-switcher-list-item]', @@ -55,8 +56,7 @@ import { KbqAppSwitcherApp } from './app-switcher'; export class KbqAppSwitcherListItem extends KbqDropdownItem { @Input() app: KbqAppSwitcherApp; @Input({ transform: booleanAttribute }) toggle = false; - - @Input() collapsed: boolean = false; + @Input({ transform: booleanAttribute }) collapsed: boolean = false; clickHandler(event: MouseEvent) { if (this.toggle) { diff --git a/packages/components/core/pop-up/pop-up-trigger.ts b/packages/components/core/pop-up/pop-up-trigger.ts index caca32a7f..be6d087a7 100644 --- a/packages/components/core/pop-up/pop-up-trigger.ts +++ b/packages/components/core/pop-up/pop-up-trigger.ts @@ -209,13 +209,13 @@ export abstract class KbqPopUpTrigger implements OnInit, OnDestroy { } } - handleKeydown(event: KeyboardEvent) { + keydownHandler(event: KeyboardEvent) { if (this.isOpen && event.keyCode === ESCAPE) { this.hide(); } } - handleTouchend() { + touchendHandler() { this.hide(); } diff --git a/packages/components/navbar/navbar-item.component.ts b/packages/components/navbar/navbar-item.component.ts index 4095f9b0e..fb27f7254 100644 --- a/packages/components/navbar/navbar-item.component.ts +++ b/packages/components/navbar/navbar-item.component.ts @@ -561,7 +561,7 @@ export class KbqNavbarItem extends KbqTooltipTrigger implements AfterContentInit '(keydown)': 'onKeydown($event)', '(click)': 'toggle()', - '(touchend)': 'handleTouchend()' + '(touchend)': 'touchendHandler()' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None @@ -612,7 +612,7 @@ export class KbqNavbarToggle extends KbqTooltipTrigger implements OnDestroy { $event.preventDefault(); } - super.handleKeydown($event); + super.keydownHandler($event); } ngOnDestroy(): void { diff --git a/packages/components/popover/popover-confirm.component.ts b/packages/components/popover/popover-confirm.component.ts index 815aac9aa..21949c3e6 100644 --- a/packages/components/popover/popover-confirm.component.ts +++ b/packages/components/popover/popover-confirm.component.ts @@ -39,8 +39,8 @@ export class KbqPopoverConfirmComponent extends KbqPopoverComponent { exportAs: 'kbqPopoverConfirm', host: { '[class.kbq-popover_open]': 'isOpen', - '(keydown)': 'handleKeydown($event)', - '(touchend)': 'handleTouchend()' + '(keydown)': 'keydownHandler($event)', + '(touchend)': 'touchendHandler()' } }) export class KbqPopoverConfirmTrigger extends KbqPopoverTrigger { diff --git a/packages/components/popover/popover.component.ts b/packages/components/popover/popover.component.ts index 206f27d79..b7312823d 100644 --- a/packages/components/popover/popover.component.ts +++ b/packages/components/popover/popover.component.ts @@ -155,8 +155,8 @@ export function getKbqPopoverInvalidPositionError(position: string) { host: { '[class.kbq-popover_open]': 'isOpen', '[class.kbq-active]': 'hasClickTrigger && isOpen', - '(keydown)': 'handleKeydown($event)', - '(touchend)': 'handleTouchend()' + '(keydown)': 'keydownHandler($event)', + '(touchend)': 'touchendHandler()' } }) export class KbqPopoverTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { diff --git a/packages/components/tooltip/tooltip.component.ts b/packages/components/tooltip/tooltip.component.ts index 315e8b8ad..18e3aade7 100644 --- a/packages/components/tooltip/tooltip.component.ts +++ b/packages/components/tooltip/tooltip.component.ts @@ -126,8 +126,8 @@ export const KBQ_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER = { host: { '[class.kbq-tooltip_open]': 'isOpen', - '(keydown)': 'handleKeydown($event)', - '(touchend)': 'handleTouchend()' + '(keydown)': 'keydownHandler($event)', + '(touchend)': 'touchendHandler()' } }) export class KbqTooltipTrigger extends KbqPopUpTrigger implements AfterViewInit, OnDestroy { @@ -383,8 +383,8 @@ export class KbqTooltipTrigger extends KbqPopUpTrigger impl host: { '[class.kbq-tooltip_open]': 'isOpen', - '(keydown)': 'handleKeydown($event)', - '(touchend)': 'handleTouchend()' + '(keydown)': 'keydownHandler($event)', + '(touchend)': 'touchendHandler()' } }) export class KbqWarningTooltipTrigger extends KbqTooltipTrigger { @@ -409,8 +409,8 @@ export class KbqWarningTooltipTrigger extends KbqTooltipTrigger { host: { '[class.kbq-tooltip_open]': 'isOpen', - '(keydown)': 'handleKeydown($event)', - '(touchend)': 'handleTouchend()' + '(keydown)': 'keydownHandler($event)', + '(touchend)': 'touchendHandler()' } }) export class KbqExtendedTooltipTrigger extends KbqTooltipTrigger { diff --git a/packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts b/packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts index 42a1491b8..6ce483feb 100644 --- a/packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts +++ b/packages/docs-examples/components/app-switcher/app-switcher-sites/app-switcher-sites-example.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; -import { KbaAppSwitcherSite, KbqAppSwitcherApp, KbqAppSwitcherModule } from '@koobiq/components/app-switcher'; +import { KbqAppSwitcherApp, KbqAppSwitcherModule, KbqAppSwitcherSite } from '@koobiq/components/app-switcher'; import { KbqAutocompleteModule } from '@koobiq/components/autocomplete'; import { KbqButtonModule } from '@koobiq/components/button'; import { KbqFormsModule } from '@koobiq/components/core'; @@ -8,9 +8,7 @@ import { KbqFormFieldModule } from '@koobiq/components/form-field'; import { KbqIcon } from '@koobiq/components/icon'; import { KbqInputModule } from '@koobiq/components/input'; -/** - * @title app-switcher-sites - */ +/** @title app-switcher-sites */ @Component({ changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, @@ -37,7 +35,7 @@ import { KbqInputModule } from '@koobiq/components/input'; ` }) export class AppSwitcherSitesExample { - sites: KbaAppSwitcherSite[] = [ + sites: KbqAppSwitcherSite[] = [ { name: 'СЗФО', id: '02', @@ -365,6 +363,6 @@ export class AppSwitcherSitesExample { } ]; - selectedSite: KbaAppSwitcherSite = this.sites[0]; + selectedSite: KbqAppSwitcherSite = this.sites[0]; selectedApp: KbqAppSwitcherApp = this.sites[0].apps[0]; } diff --git a/tools/public_api_guard/components/app-switcher.api.md b/tools/public_api_guard/components/app-switcher.api.md index 1dc5356aa..9d1a398e9 100644 --- a/tools/public_api_guard/components/app-switcher.api.md +++ b/tools/public_api_guard/components/app-switcher.api.md @@ -11,15 +11,6 @@ import { CdkScrollable } from '@angular/cdk/overlay'; import { EventEmitter } from '@angular/core'; import { FormControl } from '@angular/forms'; import * as i0 from '@angular/core'; -import * as i10 from '@angular/common'; -import * as i11 from '@koobiq/components/form-field'; -import * as i12 from '@koobiq/components/input'; -import * as i13 from '@angular/forms'; -import * as i5 from '@angular/cdk/overlay'; -import * as i6 from '@koobiq/components/button'; -import * as i7 from '@angular/cdk/a11y'; -import * as i8 from '@koobiq/components/icon'; -import * as i9 from '@angular/cdk/observers'; import { InjectionToken } from '@angular/core'; import { KbqDropdownItem } from '@koobiq/components/dropdown'; import { KbqDropdownTrigger } from '@koobiq/components/dropdown'; @@ -37,72 +28,20 @@ import { Subscription } from 'rxjs'; import { TemplateRef } from '@angular/core'; import { Type } from '@angular/core'; -// @public (undocumented) +// @public export function defaultGroupBy(app: KbqAppSwitcherApp, groups: Record, untyped: KbqAppSwitcherApp[]): void; -// @public (undocumented) -export interface KbaAppSwitcherSite { - // (undocumented) - apps: KbqAppSwitcherApp[]; - // (undocumented) - icon?: string; - // (undocumented) - id: string | number; - // (undocumented) - name: string; - // (undocumented) - status?: string; -} - -// @public (undocumented) +// @public export const KBQ_APP_SWITCHER_SCROLL_STRATEGY: InjectionToken<() => ScrollStrategy>; -// @public (undocumented) +// @public export const KBQ_APP_SWITCHER_SCROLL_STRATEGY_FACTORY_PROVIDER: { provide: InjectionToken<() => ScrollStrategy>; deps: (typeof Overlay)[]; useFactory: typeof kbqAppSwitcherScrollStrategyFactory; }; -// @public (undocumented) -export class KbqAppSwitcher extends KbqPopUp implements AfterViewInit { - // (undocumented) - protected activeApp: KbqAppSwitcherApp; - // (undocumented) - protected activeSite: KbaAppSwitcherSite; - // (undocumented) - readonly filteredSites: Observable; - // (undocumented) - input: KbqInput; - // (undocumented) - isTrapFocus: boolean; - // (undocumented) - ngAfterViewInit(): void; - // (undocumented) - onEscape(): void; - // (undocumented) - otherSites: KbqDropdownTrigger; - // (undocumented) - prefix: string; - // (undocumented) - readonly searchControl: FormControl; - // (undocumented) - selectAppInSite(site: any, app: KbqAppSwitcherApp): void; - // (undocumented) - trigger: KbqAppSwitcherTrigger; - // (undocumented) - updateClassMap(placement: string, customClass: string, size: PopUpSizes): void; - // (undocumented) - updateTrapFocus(isTrapFocus: boolean): void; - // (undocumented) - get withSites(): boolean; - // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; - // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; -} - -// @public (undocumented) +// @public export const kbqAppSwitcherAnimations: { readonly state: AnimationTriggerMetadata; }; @@ -125,7 +64,30 @@ export interface KbqAppSwitcherApp { type?: string | number; } -// @public (undocumented) +// @public +export class KbqAppSwitcherComponent extends KbqPopUp implements AfterViewInit { + protected activeApp: KbqAppSwitcherApp; + protected activeSite: KbqAppSwitcherSite; + escapeHandler(): void; + readonly filteredSites: Observable; + input: KbqInput; + isTrapFocus: boolean; + // (undocumented) + ngAfterViewInit(): void; + otherSites: KbqDropdownTrigger; + prefix: string; + readonly searchControl: FormControl; + selectAppInSite(site: KbqAppSwitcherSite, app: KbqAppSwitcherApp): void; + trigger: KbqAppSwitcherTrigger; + updateClassMap(placement: string, customClass: string, size: PopUpSizes): void; + updateTrapFocus(isTrapFocus: boolean): void; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public export class KbqAppSwitcherDropdownApp extends KbqDropdownItem { // (undocumented) app: KbqAppSwitcherApp; @@ -135,17 +97,17 @@ export class KbqAppSwitcherDropdownApp extends KbqDropdownItem { static ɵfac: i0.ɵɵFactoryDeclaration; } -// @public (undocumented) +// @public export class KbqAppSwitcherDropdownSite extends KbqDropdownItem { // (undocumented) - site: KbaAppSwitcherSite; + site: KbqAppSwitcherSite; // (undocumented) static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } -// @public (undocumented) +// @public export class KbqAppSwitcherListItem extends KbqDropdownItem { // (undocumented) app: KbqAppSwitcherApp; @@ -154,6 +116,8 @@ export class KbqAppSwitcherListItem extends KbqDropdownItem { // (undocumented) collapsed: boolean; // (undocumented) + static ngAcceptInputType_collapsed: unknown; + // (undocumented) static ngAcceptInputType_toggle: unknown; // (undocumented) toggle: boolean; @@ -175,44 +139,44 @@ export class KbqAppSwitcherModule { // Warning: (ae-forgotten-export) The symbol "i4" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } -// @public (undocumented) +// @public export function kbqAppSwitcherScrollStrategyFactory(overlay: Overlay): () => ScrollStrategy; // @public (undocumented) -export class KbqAppSwitcherTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { +export interface KbqAppSwitcherSite { + // (undocumented) + apps: KbqAppSwitcherApp[]; + // (undocumented) + icon?: string; + // (undocumented) + id: string | number; // (undocumented) + name: string; + // (undocumented) + status?: string; +} + +// @public (undocumented) +export class KbqAppSwitcherTrigger extends KbqPopUpTrigger implements AfterContentInit, OnInit { get apps(): KbqAppSwitcherApp[]; set apps(apps: KbqAppSwitcherApp[]); - // (undocumented) get appsCount(): number; - // (undocumented) arrow: boolean; - // (undocumented) backdropClass: string; - // (undocumented) closingActions(): Observable; - // (undocumented) content: string | TemplateRef; - // (undocumented) get currentApps(): KbqAppSwitcherApp[]; - // (undocumented) customClass: string; - // (undocumented) get disabled(): boolean; set disabled(value: boolean); - // (undocumented) footer: string | TemplateRef; - // (undocumented) - getOverlayHandleComponentType(): Type; - // (undocumented) + getOverlayHandleComponentType(): Type; get groupBy(): (app: KbqAppSwitcherApp, groups: Record, untyped: KbqAppSwitcherApp[]) => void; set groupBy(fn: (app: KbqAppSwitcherApp, groups: Record, untyped: KbqAppSwitcherApp[]) => void); - // (undocumented) get hasClickTrigger(): boolean; - // (undocumented) header: string | TemplateRef; // (undocumented) static ngAcceptInputType_disabled: unknown; @@ -222,60 +186,40 @@ export class KbqAppSwitcherTrigger extends KbqPopUpTrigger imple ngAfterContentInit(): void; // (undocumented) ngOnInit(): void; - // (undocumented) offset: number | null; - // (undocumented) originalApps: KbqAppSwitcherApp[]; - // (undocumented) - originalSites: KbaAppSwitcherSite[]; - // (undocumented) + originalSites: KbqAppSwitcherSite[]; protected originSelector: string; - // (undocumented) protected get overlayConfig(): OverlayConfig; - // (undocumented) placement: PopUpPlacements; - // (undocumented) readonly placementChange: EventEmitter; - // (undocumented) protected preventClosingByInnerScrollSubscription: Subscription; - // (undocumented) protected scrollStrategy: () => ScrollStrategy; - // (undocumented) selectedApp: KbqAppSwitcherApp; - // (undocumented) readonly selectedAppChange: EventEmitter; - // (undocumented) - get selectedSite(): KbaAppSwitcherSite; - set selectedSite(value: KbaAppSwitcherSite); - // (undocumented) - readonly selectedSiteChange: EventEmitter; - // (undocumented) - get sites(): KbaAppSwitcherSite[]; - set sites(value: KbaAppSwitcherSite[]); - // (undocumented) + get selectedSite(): KbqAppSwitcherSite; + set selectedSite(value: KbqAppSwitcherSite); + readonly selectedSiteChange: EventEmitter; + get sites(): KbqAppSwitcherSite[]; + set sites(value: KbqAppSwitcherSite[]); get sitesMode(): boolean; - // (undocumented) trigger: string; - // (undocumented) updateClassMap(newPlacement?: string): void; - // (undocumented) updateData(): void; updatePosition(reapplyPosition?: boolean): void; - // (undocumented) readonly visibleChange: EventEmitter; - // (undocumented) get withSearch(): boolean; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } // @public (undocumented) -export const MIN_APPS_FOR_ENABLE_GROUPING: number; +export const MIN_NUMBER_OF_APPS_TO_ENABLE_GROUPING: number; // @public (undocumented) -export const MIN_APPS_FOR_ENABLE_SEARCH: number; +export const MIN_NUMBER_OF_APPS_TO_ENABLE_SEARCH: number; // (No @packageDocumentation comment for this package) diff --git a/tools/public_api_guard/components/core.api.md b/tools/public_api_guard/components/core.api.md index 2a49f2224..024d0ebfa 100644 --- a/tools/public_api_guard/components/core.api.md +++ b/tools/public_api_guard/components/core.api.md @@ -2584,10 +2584,6 @@ export abstract class KbqPopUpTrigger implements OnInit, OnDestroy { // (undocumented) protected getPriorityPlacementStrategy(value: string | string[]): ConnectionPositionPair[]; // (undocumented) - handleKeydown(event: KeyboardEvent): void; - // (undocumented) - handleTouchend(): void; - // (undocumented) hide: (delay?: number) => void; protected hideWithTimeout: boolean; // (undocumented) @@ -2600,6 +2596,8 @@ export abstract class KbqPopUpTrigger implements OnInit, OnDestroy { // (undocumented) isOpen: boolean; // (undocumented) + keydownHandler(event: KeyboardEvent): void; + // (undocumented) leaveDelay: number; // (undocumented) protected listeners: Map; @@ -2645,6 +2643,8 @@ export abstract class KbqPopUpTrigger implements OnInit, OnDestroy { // (undocumented) protected strategy: FlexibleConnectedPositionStrategy; // (undocumented) + touchendHandler(): void; + // (undocumented) abstract trigger: string; // (undocumented) triggerName: string;