@ons/design-system 56.0.0 → 56.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@
5
5
  role="dialog"
6
6
  aria-labelledby="ons-modal-title"
7
7
  {% if params.attributes %}{% for attribute, value in (params.attributes.items() if params.attributes is mapping and params.attributes.items else params.attributes) %} {{attribute}}="{{value}}"{% endfor %}{% endif %}
8
+ {% if params.enableGA == true %}data-enable-ga='true'{% endif %}
8
9
  >
9
10
  <div class="ons-modal__content">
10
11
  <h2 id="ons-modal-title" class="ons-modal__title">
@@ -8,8 +8,11 @@ export default class Modal {
8
8
  this.component = component;
9
9
  this.launcher = document.querySelector(`[data-modal-id=${component.id}]`);
10
10
  this.closeButton = component.querySelector('.ons-js-modal-btn');
11
+ this.setGaAttributes = component.getAttribute('data-enable-ga');
11
12
  this.lastFocusedEl = null;
12
13
  this.dialogCSSSupported = true;
14
+ this.modalType = this.component.classList.contains('ons-js-timeout-modal') ? 'Timeout' : 'Generic';
15
+
13
16
  this.initialise();
14
17
  }
15
18
 
@@ -26,6 +29,10 @@ export default class Modal {
26
29
  if (this.closeButton) {
27
30
  this.closeButton.addEventListener('click', this.closeDialog.bind(this));
28
31
  }
32
+
33
+ if (this.modalType !== 'Timeout') {
34
+ window.addEventListener('keydown', this.escToClose.bind(this));
35
+ }
29
36
  }
30
37
 
31
38
  dialogSupported() {
@@ -60,6 +67,16 @@ export default class Modal {
60
67
  } else {
61
68
  this.component.showModal();
62
69
  }
70
+
71
+ if (this.setGaAttributes) {
72
+ if (event) {
73
+ this.component.setAttribute('data-ga-action', `Modal opened by ${event.type} event`);
74
+ } else {
75
+ this.component.setAttribute('data-ga-action', 'Modal opened by timed event');
76
+ }
77
+ this.component.setAttribute('data-ga-label', `${this.modalType} modal opened`);
78
+ this.component.setAttribute('data-ga-category', `${this.modalType} modal`);
79
+ }
63
80
  }
64
81
  }
65
82
 
@@ -96,6 +113,22 @@ export default class Modal {
96
113
 
97
114
  this.component.close();
98
115
  this.setFocusOnLastFocusedEl(this.lastFocusedEl);
116
+
117
+ if (this.setGaAttributes) {
118
+ if (event) {
119
+ this.component.setAttribute('data-ga-action', `Modal closed by ${event.type} event`);
120
+ } else {
121
+ this.component.setAttribute('data-ga-action', 'Modal closed by timed event');
122
+ }
123
+ this.component.setAttribute('data-ga-label', `${this.modalType} modal closed`);
124
+ this.component.setAttribute('data-ga-category', `${this.modalType} modal`);
125
+ }
126
+ }
127
+ }
128
+
129
+ escToClose(event) {
130
+ if (this.isDialogOpen() && event.keyCode === 27) {
131
+ this.closeDialog(event);
99
132
  }
100
133
  }
101
134
  }
@@ -55,5 +55,81 @@ describe('script: modal', () => {
55
55
  expect(activeElementId).toBe('launcher');
56
56
  });
57
57
  });
58
+
59
+ describe('when the `esc` key is pressed', () => {
60
+ beforeEach(async () => {
61
+ await page.focus('.ons-js-modal-btn');
62
+ await page.keyboard.press('Enter');
63
+ await page.keyboard.press('Escape');
64
+ });
65
+
66
+ it('closes the modal', async () => {
67
+ const modalIsVisible = await page.$eval('.ons-modal', node => node.classList.contains('ons-u-db'));
68
+ expect(modalIsVisible).toBe(false);
69
+ });
70
+ });
71
+ });
72
+
73
+ describe('when GA tracking is enabled', () => {
74
+ beforeEach(async () => {
75
+ const component = renderComponent('modal', { ...EXAMPLE_MODAL, enableGA: true });
76
+ const template = `
77
+ <div class="ons-page">
78
+ <button id="launcher" data-modal-id="dialog">Launcher</button>
79
+ ${component}
80
+ </div>
81
+ `;
82
+ await setTestPage('/test', template);
83
+ });
84
+
85
+ describe('when the modal is launched by a click event', () => {
86
+ beforeEach(async () => {
87
+ await page.focus('#launcher');
88
+ await page.keyboard.press('Enter');
89
+ });
90
+
91
+ it('has the correct attributes set on the modal', async () => {
92
+ const gaLabel = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-label'));
93
+ const gaAction = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-action'));
94
+ const gaCategory = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-category'));
95
+ expect(gaLabel).toBe('Generic modal opened');
96
+ expect(gaAction).toBe('Modal opened by click event');
97
+ expect(gaCategory).toBe('Generic modal');
98
+ });
99
+ });
100
+
101
+ describe('when the modal is closed by a click event', () => {
102
+ beforeEach(async () => {
103
+ await page.focus('#launcher');
104
+ await page.keyboard.press('Enter');
105
+ await page.click('.ons-js-modal-btn');
106
+ });
107
+
108
+ it('has the correct attributes set on the modal', async () => {
109
+ const gaLabel = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-label'));
110
+ const gaAction = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-action'));
111
+ const gaCategory = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-category'));
112
+ expect(gaLabel).toBe('Generic modal closed');
113
+ expect(gaAction).toBe('Modal closed by click event');
114
+ expect(gaCategory).toBe('Generic modal');
115
+ });
116
+ });
117
+
118
+ describe('when the modal is closed by `escape` keypress event', () => {
119
+ beforeEach(async () => {
120
+ await page.focus('#launcher');
121
+ await page.keyboard.press('Enter');
122
+ await page.keyboard.press('Escape');
123
+ });
124
+
125
+ it('has the correct attributes set on the modal', async () => {
126
+ const gaLabel = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-label'));
127
+ const gaAction = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-action'));
128
+ const gaCategory = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-category'));
129
+ expect(gaLabel).toBe('Generic modal closed');
130
+ expect(gaAction).toBe('Modal closed by keydown event');
131
+ expect(gaCategory).toBe('Generic modal');
132
+ });
133
+ });
58
134
  });
59
135
  });
@@ -20,37 +20,30 @@
20
20
  {% if group.groupTitle %}
21
21
  <h{{ titleSize }} class="ons-summary__group-title">{{ group.groupTitle }}</h{{ titleSize }}>
22
22
  {% endif %}
23
- {% if group.headers and group.rows %}
24
- <table class="ons-summary__items">
25
- <thead class="ons-u-vh">
26
- <tr>
27
- {% for header in group.headers %}
28
- <th>{{ header }}</th>
29
- {% endfor %}
30
- </tr>
31
- </thead>
23
+ {% if group.rows %}
24
+ <div class="ons-summary__items">
32
25
 
33
26
  {% for row in (group.rows if group.rows is iterable else group.rows.items()) %}
34
27
  {% set itemClass = "" %}
35
28
  {% if row.error %} {% set itemClass = " ons-summary__item--error" %}{% endif %}
36
29
  {% if row.total %} {% set itemClass = itemClass + " ons-summary__item--total" %}{% endif %}
37
30
 
38
- <tbody {% if row.id %}id="{{ row.id }}" {% endif %}class="ons-summary__item{{ itemClass }}">
39
- {% if row.errorMessage or (row.rowItems | length > 1 and row.rowTitle) %}
40
- <tr class="ons-summary__row">
41
- <th colspan="3" class="ons-summary__row-title ons-u-fs-r">{{ row.errorMessage or row.rowTitle }}</th>
42
- </tr>
31
+ <div {% if row.id %}id="{{ row.id }}" {% endif %}class="ons-summary__item{{ itemClass }}">
32
+ {% if row.errorMessage %}
33
+ <div class="ons-summary__row-title--error ons-u-fs-r">{{ row.errorMessage }}</div>
34
+ {% endif %}
35
+ {% if row.rowItems | length > 1 and row.rowTitle %}
36
+ <div class="ons-summary__row-title ons-summary__row-title--no-group-title ons-u-fs-r">{{ row.rowTitle }}</div>
43
37
  {% endif %}
44
38
 
45
39
  {% for rowItem in row.rowItems %}
46
- <tr {% if rowItem.id %}id="{{ rowItem.id }}" {% endif %}class="ons-summary__row{{ " ons-summary__row--has-values" if rowItem.valueList else "" }}">
47
- <td
48
- class="ons-summary__item-title"
40
+ <dl class="ons-summary__row{{ " ons-summary__row--has-values" if rowItem.valueList else "" }}"{% if rowItem.id %} id="{{ rowItem.id }}"{% endif %}>
41
+ <dt class="ons-summary__item-title{% if not rowItem.actions %} ons-summary__item-title--2{% endif %}"
49
42
  {% if rowItem.rowTitleAttributes %}{% for attribute, value in (rowItem.rowTitleAttributes.items() if rowItem.rowTitleAttributes is mapping and rowItem.rowTitleAttributes.items else rowItem.rowTitleAttributes) %}{{attribute}}="{{value}}" {% endfor %}{% endif %}
50
43
  >
51
44
  {% if rowItem.iconType %}
52
45
  {% from "components/icons/_macro.njk" import onsIcon %}
53
- <span class="ons-summary__item-title-icon {% if rowItem.iconType == 'check' %} ons-summary__item-title-icon--check{% endif %}">
46
+ <span class="ons-summary__item-title-icon{% if rowItem.iconType == 'check' %} ons-summary__item-title-icon--check{% endif %}">
54
47
  {{
55
48
  onsIcon({
56
49
  "iconType": rowItem.iconType
@@ -59,17 +52,18 @@
59
52
  </span>
60
53
  {% endif %}
61
54
 
62
- <div class="ons-summary__item--text{{ ' ons-summary__item-title--text' if rowItem.iconType else "" }}">{{ rowItem.rowTitle | default(row.rowTitle) | safe }}</div>
55
+ <div class="ons-summary__item--text{{ ' ons-summary__item-title--text' if rowItem.iconType else "" }}">
56
+ {{- rowItem.rowTitle | default(row.rowTitle) | safe -}}
57
+ </div>
63
58
 
64
59
  {# Render section status for mobile if is hub #}
65
60
  {% if params.hub and rowItem.valueList %}
66
- <span class="ons-u-d-no@s ons-u-fs-r"> — {{ rowItem.valueList[0].text | safe }}</span>
61
+ <span class="ons-u-d-no@m ons-u-fs-r"> — {{ rowItem.valueList[0].text | safe }}</span>
67
62
  {% endif %}
68
- </td>
63
+ </dt>
69
64
  {% if rowItem.valueList %}
70
- <td
65
+ <dd
71
66
  class="ons-summary__values"
72
- {% if rowItem.actions == null %} colspan="2"{% endif %}
73
67
  {% if rowItem.attributes %}{% for attribute, value in (rowItem.attributes.items() if rowItem.attributes is mapping and rowItem.attributes.items else rowItem.attributes) %}{{attribute}}="{{value}}" {% endfor %}{% endif %}
74
68
  >
75
69
  {% if rowItem.valueList | length == 1 %}
@@ -93,10 +87,10 @@
93
87
  {% endfor %}
94
88
  </ul>
95
89
  {% endif %}
96
- </td>
90
+ </dd>
97
91
  {% endif %}
98
92
  {% if rowItem.actions %}
99
- <td class="ons-summary__actions">
93
+ <dd class="ons-summary__actions">
100
94
  {% for action in (rowItem.actions if rowItem.actions is iterable else rowItem.actions.items()) %}
101
95
  {% if loop.index > 1 %}<span class="ons-summary__spacer"></span>{% endif %}
102
96
  <a
@@ -106,13 +100,13 @@
106
100
  {% if action.attributes %}{% for attribute, value in (action.attributes.items() if action.attributes is mapping and action.attributes.items else action.attributes) %}{{attribute}}="{{value}}" {% endfor %}{% endif %}
107
101
  >{{ action.text }}</a>
108
102
  {% endfor %}
109
- </td>
103
+ </dd>
110
104
  {% endif %}
111
- </tr>
105
+ </dl>
112
106
  {% endfor %}
113
- </tbody>
107
+ </div>
114
108
  {% endfor %}
115
- </table>
109
+ </div>
116
110
  {% elif group.placeholderText %}
117
111
  <span class="ons-summary__placeholder">{{ group.placeholderText }}</span>
118
112
  {% endif %}
@@ -203,36 +203,28 @@ describe('macro: summary', () => {
203
203
  });
204
204
 
205
205
  describe('part: row', () => {
206
- it('has the `headers` values displayed in th elements', () => {
207
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
208
-
209
- expect($('.ons-summary__items thead tr th:nth-child(1)').text()).toBe('Header 1');
210
- expect($('.ons-summary__items thead tr th:nth-child(2)').text()).toBe('Header 2');
211
- expect($('.ons-summary__items thead tr th:nth-child(3)').text()).toBe('Header 3');
212
- });
213
-
214
206
  it('has the correct row class when `error` is `true`', () => {
215
207
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
216
208
 
217
- expect($('.ons-summary__items tbody:nth-of-type(2)').hasClass('ons-summary__item--error')).toBe(true);
209
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2)').hasClass('ons-summary__item--error')).toBe(true);
218
210
  });
219
211
 
220
212
  it('has the correct row class when `total` is `true`', () => {
221
213
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
222
214
 
223
- expect($('.ons-summary__items tbody:nth-of-type(4)').hasClass('ons-summary__item--total')).toBe(true);
215
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(4)').hasClass('ons-summary__item--total')).toBe(true);
224
216
  });
225
217
 
226
218
  it('displays the `rowTitle` text', () => {
227
219
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
228
220
 
229
- expect($('.ons-summary__items tbody:nth-of-type(3) .ons-summary__row-title').text()).toBe('row title 3');
221
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(3) .ons-summary__row-title').text()).toBe('row title 3');
230
222
  });
231
223
 
232
224
  it('overrides the `rowTitle` with the `errorMessage` if provided', () => {
233
225
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_WITH_TITLE));
234
226
 
235
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__row-title').text()).toBe('there are errors');
227
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__row-title--error').text()).toBe('there are errors');
236
228
  });
237
229
 
238
230
  it('has the correct row `id` for each row', () => {
@@ -262,7 +254,7 @@ describe('macro: summary', () => {
262
254
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
263
255
 
264
256
  expect(
265
- $('.ons-summary__items tbody:nth-of-type(1) .ons-summary__item--text')
257
+ $('.ons-summary__items .ons-summary__item:nth-of-type(1) .ons-summary__item--text')
266
258
  .text()
267
259
  .trim(),
268
260
  ).toBe('row title 1');
@@ -289,12 +281,6 @@ describe('macro: summary', () => {
289
281
  expect($('.ons-summary__item--text').hasClass('ons-summary__item-title--text')).toBe(true);
290
282
  });
291
283
 
292
- it('has a colspan attribute on the values td if there are no `actions`', () => {
293
- const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
294
-
295
- expect($('.ons-summary__items tbody:nth-of-type(1) .ons-summary__values').attr('colspan')).toBe('2');
296
- });
297
-
298
284
  it('has custom `attributes`', () => {
299
285
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
300
286
 
@@ -308,7 +294,7 @@ describe('macro: summary', () => {
308
294
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
309
295
 
310
296
  expect(
311
- $('.ons-summary__items tbody:nth-of-type(1) tr .ons-summary__values .ons-summary__text')
297
+ $('.ons-summary__items .ons-summary__item:nth-of-type(1) dl .ons-summary__values .ons-summary__text')
312
298
  .text()
313
299
  .trim(),
314
300
  ).toBe('row value 1');
@@ -318,7 +304,7 @@ describe('macro: summary', () => {
318
304
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
319
305
 
320
306
  expect(
321
- $('.ons-summary__items tbody:nth-of-type(1) tr .ons-summary__values ul li')
307
+ $('.ons-summary__items .ons-summary__item:nth-of-type(1) dl .ons-summary__values ul li')
322
308
  .text()
323
309
  .trim(),
324
310
  ).toBe('other value');
@@ -327,7 +313,7 @@ describe('macro: summary', () => {
327
313
  it('wraps the `valueList` in a ul if multiple values provided', () => {
328
314
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
329
315
 
330
- expect($('.ons-summary__items tbody:nth-of-type(3) .ons-summary__values ul').length).toBe(1);
316
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(3) .ons-summary__values ul').length).toBe(1);
331
317
  });
332
318
  });
333
319
 
@@ -335,43 +321,55 @@ describe('macro: summary', () => {
335
321
  it('has a spacer element if multiple actions are provided', () => {
336
322
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
337
323
 
338
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__spacer').length).toBe(1);
324
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__spacer').length).toBe(1);
339
325
  });
340
326
 
341
327
  it('has the correct `url` for each action provided', () => {
342
328
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
343
329
 
344
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('href')).toBe(
345
- '#1',
346
- );
347
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').attr('href')).toBe('#2');
330
+ expect(
331
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('href'),
332
+ ).toBe('#1');
333
+ expect(
334
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').attr('href'),
335
+ ).toBe('#2');
348
336
  });
349
337
 
350
338
  it('has the action `text` for each action provided', () => {
351
339
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
352
340
 
353
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').text()).toBe(
354
- 'Action 1',
355
- );
356
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').text()).toBe('Action 2');
341
+ expect(
342
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').text(),
343
+ ).toBe('Action 1');
344
+ expect(
345
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').text(),
346
+ ).toBe('Action 2');
357
347
  });
358
348
 
359
349
  it('has the `aria-label` provided', () => {
360
350
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
361
351
 
362
352
  expect(
363
- $('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('aria-label'),
353
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr(
354
+ 'aria-label',
355
+ ),
364
356
  ).toBe('action aria label 1');
365
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').attr('aria-label')).toBe(
366
- 'action aria label 2',
367
- );
357
+ expect(
358
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:last-child').attr(
359
+ 'aria-label',
360
+ ),
361
+ ).toBe('action aria label 2');
368
362
  });
369
363
 
370
364
  it('has custom `attributes`', () => {
371
365
  const $ = cheerio.load(renderComponent('summary', EXAMPLE_SUMMARY_BASIC));
372
366
 
373
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('a')).toBe('abc');
374
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('b')).toBe('def');
367
+ expect(
368
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('a'),
369
+ ).toBe('abc');
370
+ expect(
371
+ $('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__actions .ons-summary__button:first-child').attr('b'),
372
+ ).toBe('def');
375
373
  });
376
374
  });
377
375
  });
@@ -423,7 +421,9 @@ describe('macro: summary', () => {
423
421
  }),
424
422
  );
425
423
 
426
- expect($('.ons-summary__items tbody:nth-of-type(2) .ons-summary__item-title span').text()).toBe(' — row value 2');
424
+ expect($('.ons-summary__items .ons-summary__item:nth-of-type(2) .ons-summary__row .ons-summary__item-title span').text()).toBe(
425
+ ' — row value 2',
426
+ );
427
427
  });
428
428
  });
429
429
 
@@ -8,6 +8,9 @@ $hub-row-spacing: 1.3rem;
8
8
  &__items {
9
9
  border-collapse: collapse;
10
10
  border-spacing: 0;
11
+ display: flex;
12
+ flex-direction: column;
13
+ margin: 0;
11
14
  width: 100%;
12
15
 
13
16
  + .ons-summary__group-title {
@@ -15,6 +18,11 @@ $hub-row-spacing: 1.3rem;
15
18
  }
16
19
  }
17
20
 
21
+ &__row {
22
+ display: flex;
23
+ margin: 0;
24
+ }
25
+
18
26
  &__item {
19
27
  line-height: 1.4;
20
28
 
@@ -28,6 +36,10 @@ $hub-row-spacing: 1.3rem;
28
36
 
29
37
  border-width: 2px;
30
38
  font-weight: 700;
39
+
40
+ .ons-summary__values {
41
+ padding-top: 23px;
42
+ }
31
43
  }
32
44
 
33
45
  &--error {
@@ -40,11 +52,16 @@ $hub-row-spacing: 1.3rem;
40
52
  padding: $summary-row-spacing 0;
41
53
  text-align: left;
42
54
  }
55
+ // reduces the gap between row title and summary title when there is no group title
56
+ &__title + &__group &__row-title--no-group-title {
57
+ padding-top: 0.5rem;
58
+ }
43
59
 
44
60
  &__item-title,
45
61
  &__values,
46
62
  &__actions {
47
63
  hyphens: manual;
64
+ margin: 0;
48
65
  overflow-wrap: break-word;
49
66
  padding: 0 0 $summary-row-spacing;
50
67
  vertical-align: top;
@@ -90,12 +107,13 @@ $hub-row-spacing: 1.3rem;
90
107
  }
91
108
 
92
109
  &__item--error & {
93
- &__row-title {
110
+ &__row-title--error {
94
111
  color: $color-errors;
95
112
  font-weight: 700;
96
113
  padding: $summary-row-spacing $summary-col-spacing;
97
114
  }
98
115
 
116
+ &__row-title,
99
117
  &__item-title,
100
118
  &__values,
101
119
  &__actions {
@@ -106,7 +124,8 @@ $hub-row-spacing: 1.3rem;
106
124
  padding-left: math.div($summary-col-spacing, 2);
107
125
  padding-right: math.div($summary-col-spacing, 2);
108
126
 
109
- &:first-child {
127
+ &:first-child,
128
+ & {
110
129
  padding-left: $summary-col-spacing;
111
130
  }
112
131
 
@@ -137,7 +156,7 @@ $hub-row-spacing: 1.3rem;
137
156
  }
138
157
 
139
158
  // Breakpoints
140
- @include mq(xxs, s, none, '<') {
159
+ @include mq(xxs, m, none, '<') {
141
160
  &__item-title,
142
161
  &__values,
143
162
  &__actions {
@@ -149,12 +168,17 @@ $hub-row-spacing: 1.3rem;
149
168
  display: none;
150
169
  }
151
170
  }
171
+
172
+ &__row {
173
+ flex-direction: column;
174
+ }
152
175
  }
153
176
 
154
- @include mq(s) {
177
+ @include mq(m) {
155
178
  &__item-title,
156
179
  &__values,
157
180
  &__actions {
181
+ flex: 2;
158
182
  padding-top: $summary-row-spacing;
159
183
  vertical-align: top;
160
184
 
@@ -164,14 +188,17 @@ $hub-row-spacing: 1.3rem;
164
188
  }
165
189
 
166
190
  &__actions {
167
- text-align: right;
191
+ display: flex;
192
+ justify-content: right;
168
193
  }
169
194
 
170
- &__row--has-values & {
171
- &__item-title,
172
- &__values {
173
- width: 50%;
174
- }
195
+ &__item-title,
196
+ &__values {
197
+ flex: 6.19;
198
+ }
199
+
200
+ &__item-title--2 {
201
+ flex: 4.5;
175
202
  }
176
203
 
177
204
  &--hub & {
@@ -180,6 +207,10 @@ $hub-row-spacing: 1.3rem;
180
207
  &__actions {
181
208
  padding-top: $hub-row-spacing;
182
209
  }
210
+
211
+ &__actions {
212
+ flex: 6;
213
+ }
183
214
  }
184
215
  }
185
216
  }
@@ -4,6 +4,7 @@
4
4
  "title": params.title,
5
5
  "btnText": params.btnText,
6
6
  "classes": "ons-js-timeout-modal",
7
+ "enableGA": params.enableGA,
7
8
  "attributes": {
8
9
  "data-redirect-url": params.redirectUrl,
9
10
  "data-server-session-expires-at": params.sessionExpiresAt,
@@ -73,7 +73,7 @@ export default class TimeoutModal {
73
73
  time = false;
74
74
  }
75
75
  if (this.modal.isDialogOpen()) {
76
- this.modal.closeDialog();
76
+ this.modal.closeDialog(event);
77
77
  }
78
78
  await this.timeout.restartTimeout(time);
79
79
  this.startTimeout();
@@ -223,4 +223,72 @@ describe('script: timeout modal', () => {
223
223
  });
224
224
  });
225
225
  });
226
+
227
+ describe('when GA tracking is enabled', () => {
228
+ beforeEach(async () => {
229
+ const component = renderComponent('timeout-modal', {
230
+ ...EXAMPLE_TIMEOUT_MODAL_BASIC,
231
+ showModalTimeInSeconds: 59,
232
+ enableGA: true,
233
+ });
234
+
235
+ const template = `
236
+ <div class="ons-page">
237
+ ${component}
238
+ </div>
239
+ `;
240
+
241
+ await setTestPage('/test', template);
242
+ });
243
+
244
+ describe('when the modal is open', () => {
245
+ beforeEach(async () => {
246
+ await page.waitForSelector('.ons-modal');
247
+ await page.waitForTimeout(1000);
248
+ });
249
+
250
+ it('has the correct attributes set on the modal', async () => {
251
+ const gaLabel = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-label'));
252
+ const gaAction = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-action'));
253
+ const gaCategory = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-category'));
254
+ expect(gaLabel).toBe('Timeout modal opened');
255
+ expect(gaAction).toBe('Modal opened by timed event');
256
+ expect(gaCategory).toBe('Timeout modal');
257
+ });
258
+ });
259
+
260
+ describe('when the modal is closed by a click event', () => {
261
+ beforeEach(async () => {
262
+ await page.waitForSelector('.ons-modal');
263
+ await page.waitForTimeout(1000);
264
+ await page.click('.ons-js-modal-btn');
265
+ });
266
+
267
+ it('has the correct attributes set on the modal', async () => {
268
+ const gaLabel = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-label'));
269
+ const gaAction = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-action'));
270
+ const gaCategory = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-category'));
271
+ expect(gaLabel).toBe('Timeout modal closed');
272
+ expect(gaAction).toBe('Modal closed by click event');
273
+ expect(gaCategory).toBe('Timeout modal');
274
+ });
275
+ });
276
+
277
+ describe('when the modal is closed by `escape` keypress event', () => {
278
+ beforeEach(async () => {
279
+ await page.waitForSelector('.ons-modal');
280
+ await page.waitForTimeout(1000);
281
+ await page.keyboard.press('Escape');
282
+ });
283
+
284
+ it('has the correct attributes set on the modal', async () => {
285
+ const gaLabel = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-label'));
286
+ const gaAction = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-action'));
287
+ const gaCategory = await page.$eval('.ons-modal', node => node.getAttribute('data-ga-category'));
288
+ expect(gaLabel).toBe('Timeout modal closed');
289
+ expect(gaAction).toBe('Modal closed by keydown event');
290
+ expect(gaCategory).toBe('Timeout modal');
291
+ });
292
+ });
293
+ });
226
294
  });