diff --git a/.storybook/stories/modal/side-panel-inline.stories.ts b/.storybook/stories/modal/side-panel-inline.stories.ts new file mode 100644 index 0000000000..1ddf2e0ea2 --- /dev/null +++ b/.storybook/stories/modal/side-panel-inline.stories.ts @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2016-2024 Broadcom. All Rights Reserved. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * This software is released under MIT license. + * The full license information can be found in LICENSE in the root directory of this project. + */ + +import { ClrSidePanel, ClrSidePanelModule, commonStringsDefault } from '@clr/angular'; +import { action } from '@storybook/addon-actions'; +import { moduleMetadata, StoryFn, StoryObj } from '@storybook/angular'; + +import { CommonModules, removeFocusOutline } from '../../helpers/common'; + +export default { + title: 'Modal/Side Panel (inline)', + decorators: [ + moduleMetadata({ + imports: [...CommonModules, ClrSidePanelModule], + }), + ], + component: ClrSidePanel, + argTypes: { + // inputs + clrSidePanelSize: { + options: ['sm', 'md', 'lg', 'xl', 'full-screen'], + control: 'radio', + }, + // outputs + clrSidePanelOpenChange: { control: { disable: true } }, + clrSidePanelAltClose: { control: { disable: true } }, + // methods + fadeDone: { control: { disable: true }, table: { disable: true } }, + open: { control: { disable: true }, table: { disable: true } }, + close: { control: { disable: true }, table: { disable: true } }, + }, + args: { + // inputs + clrSidePanelCloseButtonAriaLabel: commonStringsDefault.close, + clrSidePanelLabelledById: '', + clrSidePanelSize: 'md', + clrSidePanelPinnable: false, + clrSidePanelSkipAnimation: false, + // outputs + clrSidePanelOpenChange: action('clrSidePanelOpenChange'), + clrSidePanelAltClose: action('clrSidePanelAltClose'), + // story helpers + title: 'Side Panel Title', + body: 'Hello World!', + }, +}; + +const InlineSidePanelTemplate: StoryFn = args => ({ + template: ` +
+
+
+
+ + +

{{ title }}

+
+ {{ body }} +
+ +
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin in neque in ante placerat mattis id sed quam. + Proin rhoncus lacus et tempor dignissim. Vivamus sem quam, pellentesque aliquet suscipit eget, pellentesque + sed arcu. Vivamus in dui lectus. Suspendisse cursus est ac nisl imperdiet viverra. Aenean sagittis nibh + lacus, in eleifend urna ultrices et. Mauris porttitor nisi nec velit pharetra porttitor. Vestibulum + vulputate sollicitudin dolor ut tincidunt. Phasellus vitae blandit felis. Nullam posuere ipsum tincidunt + velit pellentesque rhoncus. Morbi faucibus ut ipsum at malesuada. Nam vestibulum felis sit amet metus + finibus hendrerit. Fusce faucibus odio eget ex vulputate rhoncus. Fusce nec aliquam leo, at suscipit diam. +
+
+
+
+
+ `, + props: args, +}); + +export const SidePanel: StoryObj = { + render: InlineSidePanelTemplate, +}; + +export const SidePanelSmall: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'sm', + clrSidePanelStaticBackdrop: true, + title: 'Small Side Panel', + body: 'This is a small side panel.', + }, +}; + +export const SidePanelMedium: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'md', + clrSidePanelStaticBackdrop: true, + title: 'Medium Side Panel', + body: 'This is a medium side panel.', + }, +}; + +export const SidePanelLarge: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'lg', + clrSidePanelStaticBackdrop: true, + title: 'Large Side Panel', + body: 'This is a large side panel.', + }, +}; + +export const SidePanelExtraLarge: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'xl', + clrSidePanelStaticBackdrop: true, + title: 'Extra-Large Side Panel', + body: 'This is a extra-large side panel.', + }, +}; + +export const SidePanelWithoutBackdrop: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'md', + clrSidePanelBackdrop: false, + title: 'Side Panel without backdrop', + body: 'This is a medium side panel without backdrop.', + }, +}; + +export const SidePanelAlternateClose: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'md', + clrSidePanelPreventClose: true, + clrSidePanelAltClose: function () { + if (confirm('Do you really want to close the side panel?')) { + this.clrSidePanelOpen = false; + } + }, + title: 'Side Panel with alternate close', + body: 'This is a medium side panel without backdrop.', + }, +}; + +export const SidePanelPinnable: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'md', + clrSidePanelBackdrop: false, + clrSidePanelPinnable: true, + title: 'Pinnable Side Panel', + body: 'This is a medium pinnable side panel without backdrop.', + }, +}; + +export const SidePanelFullScreen: StoryObj = { + render: InlineSidePanelTemplate, + play: removeFocusOutline, + args: { + clrSidePanelOpen: true, + clrSidePanelSize: 'full-screen', + clrSidePanelStaticBackdrop: true, + title: 'Full-Screen Side Panel', + body: 'This is a full-screen side panel.', + showLongPageContent: false, + }, +}; diff --git a/.storybook/stories/modal/side-panel.stories.ts b/.storybook/stories/modal/side-panel.stories.ts index 094a1e6579..c3f88f621e 100644 --- a/.storybook/stories/modal/side-panel.stories.ts +++ b/.storybook/stories/modal/side-panel.stories.ts @@ -38,6 +38,7 @@ export default { clrSidePanelCloseButtonAriaLabel: commonStringsDefault.close, clrSidePanelLabelledById: '', clrSidePanelSize: 'md', + clrSidePanelPinnable: false, clrSidePanelSkipAnimation: false, // outputs clrSidePanelOpenChange: action('clrSidePanelOpenChange'), @@ -56,6 +57,7 @@ const SidePanelTemplate: StoryFn = args => ({ ; // Warning: (ae-forgotten-export) The symbol "i1_49" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_36" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_28" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_29" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_19" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5_15" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i6_10" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i7_9" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -1017,6 +1017,7 @@ export interface ClrCommonStrings { show: string; showColumns: string; showColumnsMenuDescription: string; + sidePanelPin: string; signpostClose: string; signpostToggle: string; singleActionableAriaLabel: string; @@ -2696,11 +2697,11 @@ export class ClrLayoutModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_37" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_27" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_20" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_21" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_15" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -2811,7 +2812,7 @@ export class ClrMainContainerModule { export class ClrModal implements OnChanges, OnDestroy { // Warning: (ae-forgotten-export) The symbol "ScrollingService" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ModalStackService" needs to be exported by the entry point index.d.ts - constructor(_scrollingService: ScrollingService, commonStrings: ClrCommonStringsService, modalStackService: ModalStackService, configuration: ClrModalConfigurationService); + constructor(_scrollingService: ScrollingService, commonStrings: ClrCommonStringsService, modalStackService: ModalStackService, configuration: ClrModalConfigurationService, elementRef: ElementRef); // (undocumented) altClose: EventEmitter; // (undocumented) @@ -2850,9 +2851,15 @@ export class ClrModal implements OnChanges, OnDestroy { // (undocumented) _openChanged: EventEmitter; // (undocumented) - size: string; + pinnable: boolean; + // (undocumented) + get pinned(): boolean; + set pinned(pinned: boolean); + // (undocumented) + get size(): string; + set size(value: string); // (undocumented) - skipAnimation: string; + skipAnimation: boolean; // (undocumented) staticBackdrop: boolean; // (undocumented) @@ -2860,7 +2867,9 @@ export class ClrModal implements OnChanges, OnDestroy { // (undocumented) title: ElementRef; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + togglePinned(): void; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -2888,6 +2897,14 @@ export class ClrModalConfigurationService { static ɵprov: i0.ɵɵInjectableDeclaration; } +// @public (undocumented) +export class ClrModalHostDirective { + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + // @public (undocumented) export class ClrModalModule { constructor(); @@ -2897,9 +2914,10 @@ export class ClrModalModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_32" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_24" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_19" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -2925,12 +2943,12 @@ export class ClrNavigationModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_39" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_28" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_19" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_20" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_13" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5_10" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -3221,10 +3239,10 @@ export class ClrPopoverModule { // (undocumented) static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i2_31" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_24" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_25" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public @@ -3560,7 +3578,7 @@ export enum ClrSide { } // @public (undocumented) -export class ClrSidePanel implements OnInit { +export class ClrSidePanel implements OnInit, AfterViewInit { constructor(element: ElementRef, configuration: ClrModalConfigurationService); // (undocumented) altClose: EventEmitter; @@ -3572,8 +3590,13 @@ export class ClrSidePanel implements OnInit { get clrSidePanelBackdrop(): boolean; set clrSidePanelBackdrop(backdrop: boolean); // (undocumented) + get clrSidePanelPinnable(): boolean; + set clrSidePanelPinnable(pinnable: boolean); + // (undocumented) labelledById: string; // (undocumented) + ngAfterViewInit(): void; + // (undocumented) ngOnInit(): void; // (undocumented) open(): void; @@ -3586,11 +3609,11 @@ export class ClrSidePanel implements OnInit { // (undocumented) size: string; // (undocumented) - skipAnimation: string; + skipAnimation: boolean; // (undocumented) staticBackdrop: boolean; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -3604,7 +3627,7 @@ export class ClrSidePanelModule { // Warning: (ae-forgotten-export) The symbol "i1_47" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -3649,10 +3672,10 @@ export class ClrSignpostModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_44" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_32" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_23" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_24" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -3902,13 +3925,13 @@ export class ClrStepperModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_48" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_35" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_27" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_28" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_18" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5_14" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i8_9" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -4133,7 +4156,7 @@ export class ClrTabsModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_41" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_29" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_21" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_22" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_14" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5_11" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i6_8" needs to be exported by the entry point index.d.ts @@ -4143,7 +4166,7 @@ export class ClrTabsModule { // Warning: (ae-forgotten-export) The symbol "i13_3" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -4208,12 +4231,12 @@ export class ClrTimelineModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_51" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_37" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_29" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_30" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_20" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5_16" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -4318,10 +4341,10 @@ export class ClrTooltipModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_45" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_33" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_25" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_26" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -4560,12 +4583,12 @@ export class ClrVerticalNavModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_43" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_30" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_22" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_23" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_16" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5_12" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -4639,7 +4662,7 @@ export class ClrWizard implements OnDestroy, AfterContentInit, DoCheck { get stopCancel(): boolean; set stopCancel(value: boolean); // (undocumented) - get stopModalAnimations(): string; + get stopModalAnimations(): boolean; _stopModalAnimations: boolean; get stopNavigation(): boolean; set stopNavigation(value: boolean); @@ -4727,7 +4750,7 @@ export class ClrWizardModule { static ɵinj: i0.ɵɵInjectorDeclaration; // Warning: (ae-forgotten-export) The symbol "i1_46" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2_34" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "i3_26" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i3_27" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i4_17" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5_13" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i6_9" needs to be exported by the entry point index.d.ts @@ -4738,7 +4761,7 @@ export class ClrWizardModule { // Warning: (ae-forgotten-export) The symbol "i11_4" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public diff --git a/projects/angular/src/modal/_modal.clarity.scss b/projects/angular/src/modal/_modal.clarity.scss index df0c5f7295..b4b992e905 100644 --- a/projects/angular/src/modal/_modal.clarity.scss +++ b/projects/angular/src/modal/_modal.clarity.scss @@ -14,6 +14,31 @@ @include meta.load-css('properties.modal'); @include mixins.exports('modal.clarity') { + .clr-side-panel-pinned-sm { + padding-right: modal-variables.$clr-modal-sm-width !important; + } + + .clr-side-panel-pinned-md { + padding-right: modal-variables.$clr-modal-md-width !important; + } + + .clr-side-panel-pinned-lg { + padding-right: modal-variables.$clr-modal-lg-width !important; + } + + .clr-side-panel-pinned-xl { + padding-right: modal-variables.$clr-modal-xl-width !important; + } + + .clr-modal-host { + overflow: hidden; + position: relative; + .modal, + .modal-backdrop { + position: absolute; + } + } + .modal { position: fixed; top: 0; @@ -131,7 +156,8 @@ letter-spacing: modal-variables.$clr-modal-title-letter-spacing; } - .close { + .close, + .pinnable { font-size: initial; line-height: initial; @@ -157,6 +183,16 @@ } } } + + .pinnable { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + margin-right: tokens.$cds-global-space-6; + appearance: none; + -webkit-appearance: none; + } } .modal-title-wrapper { diff --git a/projects/angular/src/modal/index.ts b/projects/angular/src/modal/index.ts index 8f8c816d4a..de16cfd4fc 100644 --- a/projects/angular/src/modal/index.ts +++ b/projects/angular/src/modal/index.ts @@ -8,6 +8,7 @@ export * from './modal'; export * from './modal.module'; export * from './modal-configuration.service'; +export * from './modal-host.directive'; export * from './side-panel.module'; export * from './modal-body'; export * from './side-panel'; diff --git a/projects/angular/src/modal/modal-host.directive.ts b/projects/angular/src/modal/modal-host.directive.ts new file mode 100644 index 0000000000..5828aa52b4 --- /dev/null +++ b/projects/angular/src/modal/modal-host.directive.ts @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2016-2024 Broadcom. All Rights Reserved. + * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + * This software is released under MIT license. + * The full license information can be found in LICENSE in the root directory of this project. + */ + +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[clrModalHost]', + host: { '[class.clr-modal-host]': 'true' }, +}) +export class ClrModalHostDirective {} diff --git a/projects/angular/src/modal/modal.html b/projects/angular/src/modal/modal.html index 4834b69093..cdb637287e 100644 --- a/projects/angular/src/modal/modal.html +++ b/projects/angular/src/modal/modal.html @@ -28,6 +28,15 @@ - - + + diff --git a/projects/angular/src/modal/modal.module.ts b/projects/angular/src/modal/modal.module.ts index 0746ecb2f8..6bb336aa0f 100644 --- a/projects/angular/src/modal/modal.module.ts +++ b/projects/angular/src/modal/modal.module.ts @@ -13,8 +13,9 @@ import { ClrIconModule } from '../icon/icon.module'; import { CdkTrapFocusModule } from '../utils/cdk/cdk-trap-focus.module'; import { ClrModal } from './modal'; import { ClrModalBody } from './modal-body'; +import { ClrModalHostDirective } from './modal-host.directive'; -export const CLR_MODAL_DIRECTIVES: Type[] = [ClrModal, ClrModalBody]; +export const CLR_MODAL_DIRECTIVES: Type[] = [ClrModal, ClrModalBody, ClrModalHostDirective]; @NgModule({ imports: [CommonModule, CdkTrapFocusModule, ClrIconModule], diff --git a/projects/angular/src/modal/modal.ts b/projects/angular/src/modal/modal.ts index ebc5628875..c06019496d 100644 --- a/projects/angular/src/modal/modal.ts +++ b/projects/angular/src/modal/modal.ts @@ -66,25 +66,62 @@ export class ClrModal implements OnChanges, OnDestroy { @Input('clrModalClosable') closable = true; @Input('clrModalCloseButtonAriaLabel') closeButtonAriaLabel = this.commonStrings.keys.close; - @Input('clrModalSize') size: string; + @Input('clrModalStaticBackdrop') staticBackdrop = true; - @Input('clrModalSkipAnimation') skipAnimation = 'false'; + @Input('clrModalSkipAnimation') skipAnimation = false; @Input('clrModalPreventClose') stopClose = false; @Output('clrModalAlternateClose') altClose = new EventEmitter(false); @Input('clrModalLabelledById') labelledBy: string; + // For now we only want to expose this as input on the side panel + pinnable = false; + // presently this is only used by inline wizards @Input('clrModalOverrideScrollService') bypassScrollService = false; + private _pinned = false; + + private _size: string; constructor( private _scrollingService: ScrollingService, public commonStrings: ClrCommonStringsService, private modalStackService: ModalStackService, - private configuration: ClrModalConfigurationService + private configuration: ClrModalConfigurationService, + private elementRef: ElementRef ) {} + @Input('clrModalSize') + get size(): string { + return this._size; + } + + set size(value: string) { + if (this._size !== value) { + this._size = value; + if (this.pinnable && this.pinned) { + this.displayOverlapping(); + this.displaySideBySide(); + } + } + } + + get pinned(): boolean { + return this._pinned; + } + + set pinned(pinned: boolean) { + if (this.pinnable) { + this._pinned = pinned; + if (pinned) { + this.displaySideBySide(); + } else { + this.displayOverlapping(); + } + } + } + get fadeMove(): string { return this.skipAnimation ? '' : this.configuration.fadeMove; } @@ -96,20 +133,31 @@ export class ClrModal implements OnChanges, OnDestroy { return this.configuration.backdrop; } + private get hostElement(): HTMLElement { + return (this.elementRef.nativeElement as HTMLElement).closest('.clr-modal-host') || document.body; + } + // Detect when _open is set to true and set no-scrolling to true ngOnChanges(changes: { [propName: string]: SimpleChange }): void { if (!this.bypassScrollService && changes && Object.prototype.hasOwnProperty.call(changes, '_open')) { if (changes._open.currentValue) { - this.backdrop && this._scrollingService.stopScrolling(); + !this.pinnable && this._scrollingService.stopScrolling(); this.modalStackService.trackModalOpen(this); + if (this.pinnable && this.pinned) { + this.displaySideBySide(); + } } else { this._scrollingService.resumeScrolling(); + if (this.pinnable && this.pinned) { + this.displayOverlapping(); + } } } } ngOnDestroy(): void { this._scrollingService.resumeScrolling(); + this.displayOverlapping(); } open(): void { @@ -131,7 +179,7 @@ export class ClrModal implements OnChanges, OnDestroy { } close(): void { - if (this.stopClose) { + if (this.stopClose || (this.pinnable && this.pinned)) { this.altClose.emit(false); return; } @@ -141,6 +189,10 @@ export class ClrModal implements OnChanges, OnDestroy { this._open = false; } + togglePinned() { + this.pinned = !this.pinned; + } + fadeDone(e: AnimationEvent) { if (e.toState === 'void') { // TODO: Investigate if we can decouple from animation events @@ -148,4 +200,16 @@ export class ClrModal implements OnChanges, OnDestroy { this.modalStackService.trackModalClose(this); } } + + private displaySideBySide() { + this.hostElement.classList.add('clr-side-panel-pinned-' + this.size); + } + + private displayOverlapping() { + this.hostElement.classList.forEach(className => { + if (className.startsWith('clr-side-panel-pinned-')) { + this.hostElement.classList.remove(className); + } + }); + } } diff --git a/projects/angular/src/modal/side-panel.module.ts b/projects/angular/src/modal/side-panel.module.ts index 7faab702de..689b8aca3d 100644 --- a/projects/angular/src/modal/side-panel.module.ts +++ b/projects/angular/src/modal/side-panel.module.ts @@ -18,6 +18,6 @@ export const CLR_SIDEPANEL_DIRECTIVES: Type[] = [ClrSidePanel]; @NgModule({ imports: [CommonModule, CdkTrapFocusModule, ClrIconModule, ClrModalModule], declarations: [CLR_SIDEPANEL_DIRECTIVES], - exports: [CLR_SIDEPANEL_DIRECTIVES, ClrIconModule], + exports: [CLR_SIDEPANEL_DIRECTIVES, ClrModalModule, ClrIconModule], }) export class ClrSidePanelModule {} diff --git a/projects/angular/src/modal/side-panel.ts b/projects/angular/src/modal/side-panel.ts index f890b7817b..8a32b9fea6 100644 --- a/projects/angular/src/modal/side-panel.ts +++ b/projects/angular/src/modal/side-panel.ts @@ -5,7 +5,17 @@ * The full license information can be found in LICENSE in the root directory of this project. */ -import { Component, ElementRef, EventEmitter, HostListener, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { + AfterViewInit, + Component, + ElementRef, + EventEmitter, + HostListener, + Input, + OnInit, + Output, + ViewChild, +} from '@angular/core'; import { ClrModal } from './modal'; import { ClrModalConfigurationService } from './modal-configuration.service'; @@ -17,12 +27,12 @@ import { ClrModalConfigurationService } from './modal-configuration.service'; '[class.side-panel]': 'true', }, }) -export class ClrSidePanel implements OnInit { +export class ClrSidePanel implements OnInit, AfterViewInit { @Input('clrSidePanelOpen') _open = false; @Output('clrSidePanelOpenChange') openChange = new EventEmitter(false); @Input('clrSidePanelCloseButtonAriaLabel') closeButtonAriaLabel: string | undefined; @Input('clrSidePanelSize') size: string; - @Input('clrSidePanelSkipAnimation') skipAnimation = 'false'; + @Input('clrSidePanelSkipAnimation') skipAnimation = false; @Input('clrSidePanelLabelledById') labelledById: string; @Input('clrSidePanelStaticBackdrop') staticBackdrop = false; @Input('clrSidePanelPreventClose') preventClose = false; @@ -30,22 +40,41 @@ export class ClrSidePanel implements OnInit { @ViewChild(ClrModal) private modal: ClrModal; + private _pinnable = false; + constructor(private element: ElementRef, private configuration: ClrModalConfigurationService) {} @Input() get clrSidePanelBackdrop(): boolean { return this.configuration.backdrop; } + set clrSidePanelBackdrop(backdrop: boolean) { if (backdrop !== undefined) { this.configuration.backdrop = backdrop; } } + @Input() + get clrSidePanelPinnable(): boolean { + return this._pinnable; + } + + set clrSidePanelPinnable(pinnable: boolean) { + this._pinnable = pinnable; + if (this.modal) { + this.modal.pinnable = pinnable; + } + } + ngOnInit(): void { this.configuration.fadeMove = 'fadeLeft'; } + ngAfterViewInit() { + this.modal.pinnable = this._pinnable; + } + open() { this.modal.open(); } diff --git a/projects/angular/src/utils/i18n/common-strings.default.ts b/projects/angular/src/utils/i18n/common-strings.default.ts index 684c6a5539..0933aef4e8 100644 --- a/projects/angular/src/utils/i18n/common-strings.default.ts +++ b/projects/angular/src/utils/i18n/common-strings.default.ts @@ -41,6 +41,7 @@ export const commonStringsDefault: ClrCommonStrings = { maxValue: 'Max value', modalContentStart: 'Beginning of Modal Content', modalContentEnd: 'End of Modal Content', + sidePanelPin: 'Pin Side Panel', showColumnsMenuDescription: 'Show or hide columns menu', allColumnsSelected: 'All columns selected', signpostToggle: 'Signpost Toggle', diff --git a/projects/angular/src/utils/i18n/common-strings.interface.ts b/projects/angular/src/utils/i18n/common-strings.interface.ts index e324dba373..df13e2e7f5 100644 --- a/projects/angular/src/utils/i18n/common-strings.interface.ts +++ b/projects/angular/src/utils/i18n/common-strings.interface.ts @@ -163,6 +163,11 @@ export interface ClrCommonStrings { */ modalContentEnd: string; + /** + * Side Panel pin dialog + */ + sidePanelPin: string; + /** * Datagrid Show columns menu description */ diff --git a/projects/angular/src/wizard/wizard.ts b/projects/angular/src/wizard/wizard.ts index 69eb4a8981..6fe65fff46 100644 --- a/projects/angular/src/wizard/wizard.ts +++ b/projects/angular/src/wizard/wizard.ts @@ -263,8 +263,8 @@ export class ClrWizard implements OnDestroy, AfterContentInit, DoCheck { return this.elementRef.nativeElement.classList.contains('clr-wizard--inline'); } - get stopModalAnimations(): string { - return this._stopModalAnimations ? 'true' : 'false'; + get stopModalAnimations(): boolean { + return this._stopModalAnimations; } ngAfterContentInit(): void {