Skip to content

Commit

Permalink
fix: close only last-opened modal/detail pane when escape key is pressed
Browse files Browse the repository at this point in the history
closes VPAT-735
  • Loading branch information
kevinbuhmann committed Jan 19, 2023
1 parent 28918d2 commit 5e70321
Show file tree
Hide file tree
Showing 11 changed files with 471 additions and 17 deletions.
75 changes: 75 additions & 0 deletions .storybook/stories/datagrid/modal-stacked-on-detail.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { ClrDatagridModule, ClrModalModule } from '@clr/angular';
import { Parameters } from '@storybook/addons';
import { Story } from '@storybook/angular';
import { elements } from 'helpers/elements.data';

import { setupStorybook } from '../../helpers/setup-storybook.helpers';

const story: Story = args => ({
template: `
<div><strong>This story is NOT an endorsement of this UX pattern.</strong></div>
<clr-datagrid>
<clr-dg-column [style.width.px]="250">Name</clr-dg-column>
<clr-dg-column [style.width.px]="250">Symbol</clr-dg-column>
<clr-dg-column [style.width.px]="250">Number</clr-dg-column>
<clr-dg-column>Electronegativity</clr-dg-column>
<clr-dg-row *clrDgItems="let element of elements; let index = index" [clrDgItem]="element">
<clr-dg-cell>{{element.name}}</clr-dg-cell>
<clr-dg-cell>{{element.symbol}}</clr-dg-cell>
<clr-dg-cell>{{element.number}}</clr-dg-cell>
<clr-dg-cell>
<div [style.width.%]="element.electronegativity * 100 / 4" class="electronegativity-container">
{{element.electronegativity}}
</div>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-detail *clrIfDetail="let element">
<clr-dg-detail-header>{{element.name}}</clr-dg-detail-header>
<clr-dg-detail-body>
<button type="button" class="btn btn-primary" (click)="modalOpen = true">Open Modal</button>
</clr-dg-detail-body>
</clr-dg-detail>
<clr-dg-footer>
<clr-dg-pagination #pagination>
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Elements per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{pagination.totalItems}} elements
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<clr-modal [(clrModalOpen)]="modalOpen">
<h3 class="modal-title">Modal</h3>
<div class="modal-body">
Pressing escape should only this modal, not the detail pane.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="modalOpen = false">Close</button>
</div>
</clr-modal>
`,
props: { ...args },
});

const parameters: Parameters = {
title: 'Datagrid/Modal Stacked on Detail',
argTypes: {
// story helpers
elements: { control: { disable: true }, table: { disable: true } },
},
args: {
// story helpers
elements,
},
};

setupStorybook([ClrDatagridModule, ClrModalModule], story, parameters);
95 changes: 95 additions & 0 deletions .storybook/stories/datagrid/nested-detail-in-detail.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { ClrDatagridModule, ClrModalModule } from '@clr/angular';
import { Parameters } from '@storybook/addons';
import { Story } from '@storybook/angular';
import { elements } from 'helpers/elements.data';

import { setupStorybook } from '../../helpers/setup-storybook.helpers';

const story: Story = args => ({
template: `
<div><strong>This story is NOT an endorsement of this UX pattern.</strong></div>
<clr-datagrid>
<clr-dg-column [style.width.px]="250">Name</clr-dg-column>
<clr-dg-column [style.width.px]="250">Symbol</clr-dg-column>
<clr-dg-column [style.width.px]="250">Number</clr-dg-column>
<clr-dg-column>Electronegativity</clr-dg-column>
<clr-dg-row *clrDgItems="let element of elements" [clrDgItem]="element">
<clr-dg-cell>{{element.name}}</clr-dg-cell>
<clr-dg-cell>{{element.symbol}}</clr-dg-cell>
<clr-dg-cell>{{element.number}}</clr-dg-cell>
<clr-dg-cell>
<div [style.width.%]="element.electronegativity * 100 / 4" class="electronegativity-container">
{{element.electronegativity}}
</div>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-detail *clrIfDetail="let element">
<clr-dg-detail-header>{{element.name}}</clr-dg-detail-header>
<clr-dg-detail-body>
<clr-datagrid>
<clr-dg-column>Key</clr-dg-column>
<clr-dg-column>Value</clr-dg-column>
<clr-dg-row clrDgItem="name">
<clr-dg-cell>Name</clr-dg-cell>
<clr-dg-cell>{{element.name}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-row clrDgItem="symbol">
<clr-dg-cell>Symbol</clr-dg-cell>
<clr-dg-cell>{{element.symbol}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-row clrDgItem="number">
<clr-dg-cell>Number</clr-dg-cell>
<clr-dg-cell>{{element.number}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-row clrDgItem="electronegativity">
<clr-dg-cell>Electronegativity</clr-dg-cell>
<clr-dg-cell>{{element.electronegativity}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-detail *clrIfDetail>
<clr-dg-detail-header>Nested Detail</clr-dg-detail-header>
<clr-dg-detail-body>
Pressing escape should only close this detail pane, not the parent detail pane.
</clr-dg-detail-body>
</clr-dg-detail>
</clr-datagrid>
</clr-dg-detail-body>
</clr-dg-detail>
<clr-dg-footer>
<clr-dg-pagination #pagination>
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Elements per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{pagination.totalItems}} elements
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
`,
props: { ...args },
});

const parameters: Parameters = {
title: 'Datagrid/Nested Detail in Detail',
argTypes: {
// story helpers
elements: { control: { disable: true }, table: { disable: true } },
},
args: {
// story helpers
elements,
},
};

setupStorybook([ClrDatagridModule, ClrModalModule], story, parameters);
76 changes: 76 additions & 0 deletions .storybook/stories/modal/nested-datagrid-detail.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { ClrDatagridModule, ClrModalModule } from '@clr/angular';
import { Parameters } from '@storybook/addons';
import { Story } from '@storybook/angular';
import { elements } from 'helpers/elements.data';

import { setupStorybook } from '../../helpers/setup-storybook.helpers';

const story: Story = args => ({
template: `
<div><strong>This story is NOT an endorsement of this UX pattern.</strong></div>
<button type="button" class="btn btn-primary" (click)="modalOpen = true">Open Modal</button>
<clr-modal [(clrModalOpen)]="modalOpen">
<h3 class="modal-title">Modal</h3>
<div class="modal-body">
<clr-datagrid *ngIf="modalOpen">
<clr-dg-column [style.width.px]="250">Name</clr-dg-column>
<clr-dg-column [style.width.px]="250">Symbol</clr-dg-column>
<clr-dg-column [style.width.px]="250">Number</clr-dg-column>
<clr-dg-column>Electronegativity</clr-dg-column>
<clr-dg-row *clrDgItems="let element of elements; let index = index" [clrDgItem]="element">
<clr-dg-cell>{{element.name}}</clr-dg-cell>
<clr-dg-cell>{{element.symbol}}</clr-dg-cell>
<clr-dg-cell>{{element.number}}</clr-dg-cell>
<clr-dg-cell>
<div [style.width.%]="element.electronegativity * 100 / 4" class="electronegativity-container">
{{element.electronegativity}}
</div>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-detail *clrIfDetail="let element">
<clr-dg-detail-header>{{element.name}}</clr-dg-detail-header>
<clr-dg-detail-body>
Pressing escape should only this detail pane, not the modal.<br />
{{element | json}}
</clr-dg-detail-body>
</clr-dg-detail>
<clr-dg-footer>
<clr-dg-pagination #pagination>
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Elements per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} of {{pagination.totalItems}} elements
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="modalOpen = false">Close</button>
</div>
</clr-modal>
`,
props: { ...args },
});

const parameters: Parameters = {
title: 'Modal/Nested Datagrid Detail',
argTypes: {
// story helpers
elements: { control: { disable: true }, table: { disable: true } },
},
args: {
// story helpers
elements,
},
};

setupStorybook([ClrModalModule, ClrDatagridModule], story, parameters);
57 changes: 57 additions & 0 deletions .storybook/stories/modal/stacked-modal.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { ClrComboboxModule, ClrDropdownModule, ClrModalModule } from '@clr/angular';
import { Parameters } from '@storybook/addons';
import { Story } from '@storybook/angular';
import { elements } from 'helpers/elements.data';

import { setupStorybook } from '../../helpers/setup-storybook.helpers';

const story: Story = args => ({
template: `
<div><strong>This story is NOT an endorsement of this UX pattern.</strong></div>
<button type="button" class="btn btn-primary" (click)="modal1Open = true">Open Modal 1</button>
<clr-modal [(clrModalOpen)]="modal1Open">
<h3 class="modal-title">Modal 1</h3>
<div class="modal-body">
This is modal 1.
<button type="button" class="btn btn-primary" (click)="modal2Open = true">Open Modal 2</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="modal1Open = false">Close</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="modal2Open">
<h3 class="modal-title">Modal 2</h3>
<div class="modal-body">
This is modal 2. Pressing escape should only close this modal, not both.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="modal2Open = false">Close</button>
</div>
</clr-modal>
`,
props: { ...args },
});

const parameters: Parameters = {
title: 'Modal/Stacked Modal',
argTypes: {
// story helpers
elements: { control: { disable: true }, table: { disable: true } },
},
args: {
// story helpers
elements,
},
};

setupStorybook([ClrModalModule, ClrComboboxModule, ClrDropdownModule], story, parameters);
3 changes: 2 additions & 1 deletion projects/angular/clarity.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2626,7 +2626,8 @@ export class ClrMainContainerModule {
// @public (undocumented)
export class ClrModal implements OnChanges, OnDestroy {
// Warning: (ae-forgotten-export) The symbol "ScrollingService" needs to be exported by the entry point index.d.ts
constructor(_scrollingService: ScrollingService, commonStrings: ClrCommonStringsService);
// 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);
// (undocumented)
altClose: EventEmitter<boolean>;
// (undocumented)
Expand Down
3 changes: 1 addition & 2 deletions projects/angular/src/data/datagrid/datagrid-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { Component, ContentChild, HostListener } from '@angular/core';
import { Component, ContentChild } from '@angular/core';

import { ClrCommonStringsService } from '../../utils/i18n/common-strings.service';
import { ClrDatagridDetailHeader } from './datagrid-detail-header';
Expand Down Expand Up @@ -39,7 +39,6 @@ export class ClrDatagridDetail {

constructor(public detailService: DetailService, public commonStrings: ClrCommonStringsService) {}

@HostListener('document:keyup.escape')
close(): void {
this.detailService.close();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { ModalStackService } from 'projects/angular/src/modal/modal-stack.service';
import { Subscription } from 'rxjs';

import { DetailService } from './detail.service';

// Prevent ModalStackService from adding event handlers.
const PLATFORM_SERVER_ID = 'server';

export default function (): void {
describe('DetailService provider', function () {
let provider: DetailService;
let subscription: Subscription;

beforeEach(function () {
provider = new DetailService();
provider = new DetailService(new ModalStackService(PLATFORM_SERVER_ID));
});

afterEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

import { ModalStackService } from '../../../modal/modal-stack.service';

@Injectable()
export class DetailService {
private toggleState = false;
Expand All @@ -28,9 +30,12 @@ export class DetailService {
return this._state.asObservable();
}

constructor(private readonly modalStackService: ModalStackService) {}

close() {
this.toggleState = false;
this._state.next(this.toggleState);
this.modalStackService.trackModalClose(this);
if (this.button) {
this.button.focus();
this.button = null;
Expand All @@ -42,6 +47,7 @@ export class DetailService {
this.button = button;
this.toggleState = true;
this._state.next(this.toggleState);
this.modalStackService.trackModalOpen(this);
}

toggle(item: any, button?: HTMLButtonElement) {
Expand Down

0 comments on commit 5e70321

Please sign in to comment.