diff --git a/packages/components-dev/dropdown/module.ts b/packages/components-dev/dropdown/module.ts index d937a2617..d14cd0595 100644 --- a/packages/components-dev/dropdown/module.ts +++ b/packages/components-dev/dropdown/module.ts @@ -17,6 +17,7 @@ import { DropdownOverviewExample, DropdownRecursiveTemplateExample, DropdownWithFilterExample, + DropdownWithFooterExample, DropdownXPositionExample } from 'packages/docs-examples/components/dropdown'; import { DevThemeToggle } from '../theme-toggle'; @@ -33,9 +34,13 @@ import { DevThemeToggle } from '../theme-toggle'; DropdownOpenByArrowDownExample, DropdownRecursiveTemplateExample, DropdownWithFilterExample, + DropdownWithFooterExample, DropdownXPositionExample ], template: ` + +
+
diff --git a/packages/components/code-block/__screenshots__/01-light.png b/packages/components/code-block/__screenshots__/01-light.png index f0f3a49cd..6a341eef0 100644 Binary files a/packages/components/code-block/__screenshots__/01-light.png and b/packages/components/code-block/__screenshots__/01-light.png differ diff --git a/packages/components/dropdown/__screenshots__/01-dark.png b/packages/components/dropdown/__screenshots__/01-dark.png index e117b9baf..6fc46c36b 100644 Binary files a/packages/components/dropdown/__screenshots__/01-dark.png and b/packages/components/dropdown/__screenshots__/01-dark.png differ diff --git a/packages/components/dropdown/__screenshots__/01-light.png b/packages/components/dropdown/__screenshots__/01-light.png index 522270eff..8448c8431 100644 Binary files a/packages/components/dropdown/__screenshots__/01-light.png and b/packages/components/dropdown/__screenshots__/01-light.png differ diff --git a/packages/components/dropdown/dropdown.component.ts b/packages/components/dropdown/dropdown.component.ts index bbd8f2612..322bc51ce 100644 --- a/packages/components/dropdown/dropdown.component.ts +++ b/packages/components/dropdown/dropdown.component.ts @@ -58,6 +58,13 @@ import { }) export class KbqDropdownStaticContent {} +/** Footer that is rendered below the dropdown options panel. */ +@Directive({ + selector: '[kbqDropdownFooter], kbq-dropdown-footer', + host: { class: 'kbq-dropdown-footer' } +}) +export class KbqDropdownFooter {} + @Component({ selector: 'kbq-dropdown', imports: [], diff --git a/packages/components/dropdown/dropdown.en.md b/packages/components/dropdown/dropdown.en.md index b47eecf75..fadb4b77b 100644 --- a/packages/components/dropdown/dropdown.en.md +++ b/packages/components/dropdown/dropdown.en.md @@ -10,6 +10,12 @@ Add the `progress` attribute to `kbq-dropdown-item` to show a loading shimmer on +### Footer + +You can place auxiliary elements in the footer: [buttons](en/components/button), [links](en/components/link), hints. + + + ### Open by Arrow Down ↓ diff --git a/packages/components/dropdown/dropdown.html b/packages/components/dropdown/dropdown.html index 7ed08d4f6..ac94e355c 100644 --- a/packages/components/dropdown/dropdown.html +++ b/packages/components/dropdown/dropdown.html @@ -15,6 +15,7 @@
+ diff --git a/packages/components/dropdown/dropdown.module.ts b/packages/components/dropdown/dropdown.module.ts index ca5ea1978..485b824a5 100644 --- a/packages/components/dropdown/dropdown.module.ts +++ b/packages/components/dropdown/dropdown.module.ts @@ -4,7 +4,7 @@ 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, KbqDropdownStaticContent } from './dropdown.component'; +import { KbqDropdown, KbqDropdownFooter, KbqDropdownStaticContent } from './dropdown.component'; @NgModule({ imports: [ @@ -14,7 +14,8 @@ import { KbqDropdown, KbqDropdownStaticContent } from './dropdown.component'; KbqDropdown, KbqDropdownItem, KbqDropdownTrigger, - KbqDropdownContent + KbqDropdownContent, + KbqDropdownFooter ], providers: [KBQ_DROPDOWN_SCROLL_STRATEGY_FACTORY_PROVIDER], exports: [ @@ -22,7 +23,8 @@ import { KbqDropdown, KbqDropdownStaticContent } from './dropdown.component'; KbqDropdownItem, KbqDropdownTrigger, KbqDropdownContent, - KbqDropdownStaticContent + KbqDropdownStaticContent, + KbqDropdownFooter ] }) export class KbqDropdownModule {} diff --git a/packages/components/dropdown/dropdown.ru.md b/packages/components/dropdown/dropdown.ru.md index 0ba612860..6f7ed0a32 100644 --- a/packages/components/dropdown/dropdown.ru.md +++ b/packages/components/dropdown/dropdown.ru.md @@ -10,6 +10,12 @@ +### Футер + +В нижнем колонтитуле можно разместить вспомогательные элементы: [кнопки](ru/components/button), [ссылки](ru/components/link), подсказки. + + + ### Открытие по стрелке вниз ↓ diff --git a/packages/components/dropdown/dropdown.scss b/packages/components/dropdown/dropdown.scss index d959a7dfd..c1f29bb98 100644 --- a/packages/components/dropdown/dropdown.scss +++ b/packages/components/dropdown/dropdown.scss @@ -45,7 +45,6 @@ border-radius: var(--kbq-dropdown-size-container-border-radius); box-sizing: border-box; - padding: var(--kbq-size-xxs); // Prevent users from interacting with the panel while it's animating. Note that // people won't be able to click through it, because the overlay pane will catch the click. @@ -86,6 +85,13 @@ .kbq-dropdown__content { display: flex; flex-direction: column; + + padding: var(--kbq-size-xxs); +} + +.kbq-dropdown-footer { + @extend %kbq-select-footer; + border-color: var(--kbq-line-contrast-less); } @include theme.kbq-dropdown-theme(); diff --git a/packages/components/dropdown/dropdown.spec.ts b/packages/components/dropdown/dropdown.spec.ts index 3c8de9232..bec4586ea 100644 --- a/packages/components/dropdown/dropdown.spec.ts +++ b/packages/components/dropdown/dropdown.spec.ts @@ -1196,6 +1196,27 @@ describe('KbqDropdown', () => { }); }); + describe('footer', () => { + it('should project footer content into the panel, separate from the options content', () => { + const fixture = createComponent(FooterDropdown, [], []); + + fixture.detectChanges(); + fixture.componentInstance.trigger().open(); + fixture.detectChanges(); + + const content = overlayContainerElement.querySelector('.kbq-dropdown__content') as HTMLElement; + const footer = overlayContainerElement.querySelector('.kbq-dropdown-footer') as HTMLElement; + + expect(content.textContent).toContain('Item'); + expect(footer).toBeTruthy(); + expect(footer.textContent!.trim()).toBe('Footer content'); + + // The footer must render outside the scrollable options content, not inside it. + expect(content.contains(footer)).toBe(false); + expect(content.textContent).not.toContain('Footer content'); + }); + }); + describe('nested dropdown', () => { let fixture: ComponentFixture; let instance: NestedDropdown; @@ -2068,6 +2089,20 @@ class SimpleDropdown { backdropClass: string; } +@Component({ + imports: [KbqDropdownModule], + template: ` + + + + Footer content + + ` +}) +class FooterDropdown { + readonly trigger = viewChild.required(KbqDropdownTrigger); +} + @Component({ imports: [KbqDropdownModule], template: ` diff --git a/packages/components/dropdown/e2e.ts b/packages/components/dropdown/e2e.ts index 2a1604149..14f79b8c4 100644 --- a/packages/components/dropdown/e2e.ts +++ b/packages/components/dropdown/e2e.ts @@ -71,6 +71,8 @@ import { KbqDropdownModule } from './dropdown.module'; + + Dropdown footer @@ -89,6 +91,7 @@ import { KbqDropdownModule } from './dropdown.module'; Disabled item
Caption
+ Dropdown footer
@@ -105,7 +108,7 @@ import { KbqDropdownModule } from './dropdown.module'; styles: ` :host { display: flex; - height: 500px; + height: 600px; width: 1020px; padding: var(--kbq-size-s); } diff --git a/packages/docs-examples/components/dropdown/dropdown-with-footer/dropdown-with-footer-example.ts b/packages/docs-examples/components/dropdown/dropdown-with-footer/dropdown-with-footer-example.ts new file mode 100644 index 000000000..7f066adce --- /dev/null +++ b/packages/docs-examples/components/dropdown/dropdown-with-footer/dropdown-with-footer-example.ts @@ -0,0 +1,65 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { KbqButtonModule } from '@koobiq/components/button'; +import { KbqDropdownModule } from '@koobiq/components/dropdown'; +import { KbqIconModule } from '@koobiq/components/icon'; +import { KbqLinkModule } from '@koobiq/components/link'; + +/** + * @title Dropdown with footer + */ +@Component({ + selector: 'dropdown-with-footer-example', + imports: [KbqDropdownModule, KbqButtonModule, KbqLinkModule, KbqIconModule], + template: ` + + + + + + + + + + + + GitHub + + + + + + + + + + + Footer text is multiline and wraps across lines + + + `, + styles: ` + :host { + height: 200px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + .example-submenu-footer { + font-size: var(--kbq-typography-text-compact-font-size); + color: var(--kbq-foreground-contrast-secondary); + } + `, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DropdownWithFooterExample {} diff --git a/packages/docs-examples/components/dropdown/index.ts b/packages/docs-examples/components/dropdown/index.ts index 092c6323d..14e80cab1 100644 --- a/packages/docs-examples/components/dropdown/index.ts +++ b/packages/docs-examples/components/dropdown/index.ts @@ -8,6 +8,7 @@ import { DropdownOpenByArrowDownExample } from './dropdown-open-by-arrow-down/dr import { DropdownOverviewExample } from './dropdown-overview/dropdown-overview-example'; import { DropdownRecursiveTemplateExample } from './dropdown-recursive-template/dropdown-recursive-template-example'; import { DropdownWithFilterExample } from './dropdown-with-filter/dropdown-with-filter-example'; +import { DropdownWithFooterExample } from './dropdown-with-footer/dropdown-with-footer-example'; import { DropdownXPositionExample } from './dropdown-x-position/dropdown-x-position-example'; export { @@ -20,12 +21,14 @@ export { DropdownOverviewExample, DropdownRecursiveTemplateExample, DropdownWithFilterExample, + DropdownWithFooterExample, DropdownXPositionExample }; const EXAMPLES = [ DropdownNestedExample, DropdownOverviewExample, + DropdownWithFooterExample, DropdownNavigationWrapExample, DropdownDisabledExample, DropdownItemProgressExample, diff --git a/tools/public_api_guard/components/dropdown.api.md b/tools/public_api_guard/components/dropdown.api.md index 79676bca8..d676d25af 100644 --- a/tools/public_api_guard/components/dropdown.api.md +++ b/tools/public_api_guard/components/dropdown.api.md @@ -125,7 +125,7 @@ export class KbqDropdown implements AfterContentInit, KbqDropdownPanel, OnInit, get yPosition(): KbqDropdownPositionY; set yPosition(value: KbqDropdownPositionY); // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -164,6 +164,14 @@ export interface KbqDropdownDefaultOptions { yPosition: KbqDropdownPositionY; } +// @public +export class KbqDropdownFooter { + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + // @public export class KbqDropdownItem implements KbqTitleTextRef, IFocusableOption, AfterViewInit, OnDestroy { checkDisabled(event: Event): void; @@ -210,7 +218,7 @@ export class KbqDropdownModule { // (undocumented) static ɵinj: i0.ɵɵInjectorDeclaration; // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public