@ons/design-system 73.2.0 → 73.4.0

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.
Files changed (31) hide show
  1. package/components/button/_button.scss +3 -6
  2. package/components/chart/annotations-options.js +15 -3
  3. package/components/chart/chart.js +27 -2
  4. package/components/chart/common-chart-options.js +10 -0
  5. package/components/chart/range-annotations-options.js +19 -3
  6. package/components/chart/reference-line-annotations-options.js +19 -0
  7. package/components/description-list/_description-list.scss +26 -0
  8. package/components/header/_header.scss +47 -4
  9. package/components/header/_macro.njk +168 -185
  10. package/components/header/_macro.spec.js +116 -14
  11. package/components/header/example-header-basic-with-search-no-heading.njk +22 -0
  12. package/components/header/header.spec.js +18 -1
  13. package/components/hero/example-hero-grey-with-last-updated.njk +27 -0
  14. package/components/navigation/navigation.js +20 -2
  15. package/components/navigation/navigation.spec.js +22 -0
  16. package/components/phase-banner/_macro.njk +10 -1
  17. package/components/phase-banner/_macro.spec.js +27 -0
  18. package/components/phase-banner/example-phase-banner-beta-with-feedback-link.njk +18 -0
  19. package/components/phase-banner/example-phase-banner-beta-without-feedback-link.njk +7 -0
  20. package/components/question/_question.scss +1 -0
  21. package/components/table/_macro.njk +6 -3
  22. package/components/table/_macro.spec.js +58 -0
  23. package/components/table/_table.scss +9 -1
  24. package/components/table/example-table-with-valign-property.njk +142 -0
  25. package/css/main.css +1 -1
  26. package/layout/_template.njk +1 -1
  27. package/package.json +1 -1
  28. package/scripts/main.es5.js +1 -1
  29. package/scripts/main.js +2 -2
  30. package/scss/utilities/_highlight.scss +6 -2
  31. package/scss/utilities/_typography.scss +4 -0
@@ -1,6 +1,6 @@
1
1
  import { renderComponent, setTestPage } from '../../tests/helpers/rendering';
2
2
  import { EXAMPLE_HEADER_SEARCH_AND_MENU_LINKS } from './_test-examples';
3
- import { getNodeAttributes } from '../../tests/helpers/puppeteer';
3
+ import { getNodeAttributes, setViewport } from '../../tests/helpers/puppeteer';
4
4
 
5
5
  describe('script: header', () => {
6
6
  beforeEach(async () => {
@@ -107,6 +107,23 @@ describe('script: header', () => {
107
107
  const isAriaHiddenFalse = await page.$eval('.ons-header-nav-menu', (el) => el.getAttribute('aria-hidden') === 'false');
108
108
  expect(isAriaHiddenFalse).toBe(true);
109
109
  });
110
+
111
+ describe('and only the viewport height changes', () => {
112
+ beforeEach(async () => {
113
+ const viewport = page.viewport();
114
+ await setViewport(page, { width: viewport.width, height: viewport.height - 150 });
115
+ });
116
+
117
+ it('then the navigation menu stays displayed', async () => {
118
+ const isMenuNavVisible = await page.$eval('.ons-header-nav-menu', (el) => !el.classList.contains('ons-u-d-no'));
119
+ expect(isMenuNavVisible).toBe(true);
120
+ });
121
+
122
+ it('then the menu button stays marked as expanded', async () => {
123
+ const menuButtonAriaExpanded = await page.$eval('.ons-btn--menu', (el) => el.getAttribute('aria-expanded'));
124
+ expect(menuButtonAriaExpanded).toBe('true');
125
+ });
126
+ });
110
127
  });
111
128
 
112
129
  describe('when the menu button is not clicked', () => {
@@ -0,0 +1,27 @@
1
+ ---
2
+ 'fullWidth': true
3
+ ---
4
+
5
+ {% from "components/hero/_macro.njk" import onsHero %}
6
+ {{
7
+ onsHero({
8
+ "variants": 'grey',
9
+ "detailsColumns": '12',
10
+ "title": 'Lorem Ipsum',
11
+ "text": 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
12
+ "descriptionList": {
13
+ "termCol": "5",
14
+ "descriptionCol": "7",
15
+ "itemsList": [
16
+ {
17
+ "term": "Last updated:",
18
+ "descriptions": [
19
+ {
20
+ "description": "23 February 2024"
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ })
27
+ }}
@@ -90,15 +90,33 @@ export default class NavigationToggle {
90
90
  }
91
91
 
92
92
  isHidden(el) {
93
- return el.offsetParent === null;
93
+ if (!el) {
94
+ return true;
95
+ }
96
+
97
+ const style = window.getComputedStyle(el);
98
+ if (style.display === 'none') {
99
+ return true;
100
+ }
101
+
102
+ // `offsetParent` can be `null` for reasons other than `display: none` (e.g. fixed positioning),
103
+ // so use layout rects as a more reliable proxy for visibility.
104
+ return el.getClientRects().length === 0;
94
105
  }
95
106
 
96
- setAria() {
107
+ setAria(viewportDetails = null) {
97
108
  const isToggleHidden = this.isHidden(this.toggle);
98
109
  this.toggle.setAttribute(attrDisabled, 'false');
99
110
 
100
111
  this.searchToggleBtn?.setAttribute(attrDisabled, 'false');
101
112
 
113
+ // iOS Safari/Chrome can change viewport height during scroll when the browser chrome
114
+ // hides/shows, which can trigger resize events. If the user has the navigation open
115
+ // on a small screen, don't force-close it in response to these resizes.
116
+ if (viewportDetails && !isToggleHidden && this.navigation.getAttribute(attrHidden) === 'false') {
117
+ return;
118
+ }
119
+
102
120
  if (!isToggleHidden) {
103
121
  // close nav by default if toggle button is visible
104
122
  this.closeNav();
@@ -259,6 +259,28 @@ describe('script: navigation', () => {
259
259
  expect(hasClass).toBe(false);
260
260
  });
261
261
  });
262
+
263
+ describe('when the navigation is open and only the viewport height changes', () => {
264
+ beforeEach(async () => {
265
+ await page.focus(buttonEl);
266
+ await page.keyboard.press('Enter');
267
+ // Simulate iOS address bar show/hide which changes height during scroll
268
+ // without any meaningful layout/breakpoint change.
269
+ await setViewport(page, { width: 600, height: 900 });
270
+ });
271
+
272
+ it('keeps aria-hidden set as `false` on the navigation list', async () => {
273
+ const nav = await page.$(navEl);
274
+ const hasAriaAttribute = await nav.evaluate((node) => node.getAttribute('aria-hidden') === 'false');
275
+ expect(hasAriaAttribute).toBe(true);
276
+ });
277
+
278
+ it('keeps aria-expanded set as `true` on the navigation toggle button', async () => {
279
+ const button = await page.$(buttonEl);
280
+ const ariaExpandedIsTrue = await button.evaluate((node) => node.getAttribute('aria-expanded') === 'true');
281
+ expect(ariaExpandedIsTrue).toBe(true);
282
+ });
283
+ });
262
284
  });
263
285
  });
264
286
 
@@ -8,7 +8,16 @@
8
8
  </div>
9
9
  {% endif %}
10
10
  <div class="ons-grid__col ons-col-auto ons-u-flex-shrink">
11
- <p class="ons-phase-banner__desc ons-u-fs-s ons-u-mb-no">{{ params.html | safe }}</p>
11
+ <p class="ons-phase-banner__desc ons-u-fs-s ons-u-mb-no">
12
+ {% if params.html %}
13
+ {{ params.html | safe }}
14
+ {% else %}
15
+ {{ params.text }}
16
+ {% if params.feedbackLink %}
17
+ {{ params.feedbackLink | safe }}
18
+ {% endif %}
19
+ {% endif %}
20
+ </p>
12
21
  </div>
13
22
  </div>
14
23
  </div>
@@ -24,6 +24,33 @@ describe('macro: phase-banner', () => {
24
24
  expect(htmlContent).toBe('Example content with a <a href="#">link</a>');
25
25
  });
26
26
 
27
+ it('renders text content when `text` is provided', () => {
28
+ const $ = cheerio.load(
29
+ renderComponent('phase-banner', {
30
+ text: 'Example text content',
31
+ }),
32
+ );
33
+
34
+ const textContent = $('.ons-phase-banner__desc').text().trim();
35
+ expect(textContent).toBe('Example text content');
36
+ });
37
+
38
+ it('renders feedbackLink when provided with text', () => {
39
+ const $ = cheerio.load(
40
+ renderComponent('phase-banner', {
41
+ text: 'Example text content',
42
+ feedbackLink: '<a href="#">give feedback</a>',
43
+ }),
44
+ );
45
+
46
+ const textContent = $('.ons-phase-banner__desc').text().replace(/\s+/g, ' ').trim();
47
+ expect(textContent).toBe('Example text content give feedback');
48
+
49
+ const link = $('.ons-phase-banner__desc a');
50
+ expect(link.length).toBe(1);
51
+ expect(link.text()).toBe('give feedback');
52
+ });
53
+
27
54
  it('has the "Beta" badge by default', () => {
28
55
  const $ = cheerio.load(renderComponent('phase-banner', EXAMPLE_PHASE_BANNER_MINIMAL));
29
56
 
@@ -0,0 +1,18 @@
1
+ {% from "components/phase-banner/_macro.njk" import onsPhaseBanner %}
2
+ {% from "components/external-link/_macro.njk" import onsExternalLink %}
3
+
4
+ {% set feedbackLink %}
5
+ {{
6
+ onsExternalLink({
7
+ "url": "#0",
8
+ "text": "give feedback"
9
+ })
10
+ }}
11
+ {% endset %}
12
+
13
+ {{
14
+ onsPhaseBanner({
15
+ "text": "This is a new service. To help us improve it,",
16
+ "feedbackLink": feedbackLink
17
+ })
18
+ }}
@@ -0,0 +1,7 @@
1
+ {% from "components/phase-banner/_macro.njk" import onsPhaseBanner %}
2
+
3
+ {{
4
+ onsPhaseBanner({
5
+ "text": "This is a new service"
6
+ })
7
+ }}
@@ -7,6 +7,7 @@
7
7
  mark,
8
8
  .ons-instruction {
9
9
  background-color: var(--ons-color-instruction);
10
+ background-image: none;
10
11
  color: var(--ons-color-text-inverse);
11
12
  margin-right: 0.5rem;
12
13
  padding: 0 0.5rem;
@@ -35,7 +35,7 @@
35
35
  {% for th in headerCols.ths %}
36
36
  <th
37
37
  scope="col"
38
- class="ons-table__header{{ ' ' + th.thClasses if th.thClasses else '' }}{{ ' ons-table__header--numeric' if th.numeric }}"
38
+ class="ons-table__header ons-table__header--{{ th.valign | default('top') }}{{ ' ' + th.thClasses if th.thClasses else '' }}{{ ' ons-table__header--numeric' if th.numeric }}"
39
39
  {% if th.colspan %}colspan="{{ th.colspan }}"{% endif %}
40
40
  {% if th.rowspan %}rowspan="{{ th.rowspan }}"{% endif %}
41
41
  {% if 'sortable' in variants %}aria-sort="{{ th.ariaSort | default('none') }}"{% endif %}
@@ -73,7 +73,8 @@
73
73
  {% set isFirstRow = loop.index == 0 and td.rowspan %}
74
74
  {% set rowSpan = isFirstRow and loop.index0 == rowspanCount %}
75
75
  {{ openingTag | safe }}
76
- class="ons-table__cell{{ ' ' + td.tdClasses if td.tdClasses else '' }}{{ ' ons-table__cell--numeric' if td.numeric }}"
76
+ class="ons-table__cell
77
+ ons-table__cell--{{ td.valign | default('top') }}{{ ' ' + td.tdClasses if td.tdClasses else '' }}{{ ' ons-table__cell--numeric' if td.numeric }}"
77
78
  {% if td.id %}id="{{ td.id }}"{% endif %}
78
79
  {% if td.data %}{{ ' ' }}data-th="{{ td.data }}"{% endif %}
79
80
  {% if td.colspan %}colspan="{{ td.colspan }}"{% endif %}
@@ -107,7 +108,9 @@
107
108
  <tfoot class="ons-table__foot">
108
109
  <tr class="ons-table__row">
109
110
  {% for tfootCell in params.tfoot %}
110
- <td class="ons-table__cell ons-u-fs-s">{{ tfootCell.value }}</td>
111
+ <td class="ons-table__cell ons-table__cell--{{ tfootCell.valign | default('top') }} ons-u-fs-s">
112
+ {{ tfootCell.value }}
113
+ </td>
111
114
  {% endfor %}
112
115
  </tr>
113
116
  </tfoot>
@@ -90,6 +90,51 @@ describe('macro: table', () => {
90
90
  expect($('.ons-table-scrollable__content').attr('aria-label')).toBe('Example table caption. Special table');
91
91
  });
92
92
 
93
+ describe('Vertical Alignment', () => {
94
+ it('adds "ons-table__header--middle" class to column header when valign is set', () => {
95
+ const $ = cheerio.load(
96
+ renderComponent('table', {
97
+ ...EXAMPLE_TABLE,
98
+ ths: [
99
+ {
100
+ value: 'Column 1',
101
+ valign: 'middle',
102
+ },
103
+ ],
104
+ }),
105
+ );
106
+
107
+ expect($('.ons-table__header').hasClass('ons-table__header--middle')).toBe(true);
108
+ });
109
+
110
+ it('adds "ons-table__header--top" class to column header when valign is not set', () => {
111
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
112
+
113
+ expect($('.ons-table__header').hasClass('ons-table__header--top')).toBe(true);
114
+ });
115
+
116
+ it('adds "ons-table__cell--middle" class to row when valign is middle', () => {
117
+ const $ = cheerio.load(
118
+ renderComponent('table', {
119
+ ...EXAMPLE_TABLE,
120
+ trs: [
121
+ {
122
+ tds: [{ value: 'Row 1 Cell 1', valign: 'middle' }],
123
+ },
124
+ ],
125
+ }),
126
+ );
127
+
128
+ expect($('.ons-table__cell').hasClass('ons-table__cell--middle')).toBe(true);
129
+ });
130
+
131
+ it('adds "ons-table__cell--top" class to row when valign is not set', () => {
132
+ const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
133
+
134
+ expect($('.ons-table__cell').hasClass('ons-table__cell--top')).toBe(true);
135
+ });
136
+ });
137
+
93
138
  describe('header row', () => {
94
139
  it('renders header cells with expected text', () => {
95
140
  const $ = cheerio.load(renderComponent('table', EXAMPLE_TABLE));
@@ -529,6 +574,19 @@ describe('macro: table', () => {
529
574
  const footerCellValues = mapAll($('.ons-table__foot .ons-table__cell'), (node) => node.text().trim());
530
575
  expect(footerCellValues).toEqual(['Footer Cell 1', 'Footer Cell 2', 'Footer Cell 3']);
531
576
  });
577
+ it('adds "ons-table__cell--middle" class to row when valign is middle', () => {
578
+ const $ = cheerio.load(
579
+ renderComponent('table', {
580
+ ...EXAMPLE_TABLE,
581
+ tfoot: [
582
+ { value: 'Footer Cell 1', valign: 'middle' },
583
+ { value: 'Footer Cell 2', valign: 'middle' },
584
+ ],
585
+ }),
586
+ );
587
+
588
+ expect($('.ons-table__cell').hasClass('ons-table__cell--middle')).toBe(true);
589
+ });
532
590
  });
533
591
 
534
592
  describe('sortable variant', () => {
@@ -26,10 +26,18 @@
26
26
  overflow: hidden;
27
27
  padding: 0.5rem 0 0.5rem 1rem;
28
28
  text-align: left;
29
- vertical-align: top;
30
29
  &--numeric {
31
30
  text-align: right;
32
31
  }
32
+ &--top {
33
+ vertical-align: top;
34
+ }
35
+ &--bottom {
36
+ vertical-align: bottom;
37
+ }
38
+ &--center {
39
+ vertical-align: middle;
40
+ }
33
41
  }
34
42
 
35
43
  &--has-rowspan {
@@ -0,0 +1,142 @@
1
+ {% from "components/table/_macro.njk" import onsTable %}
2
+ {{
3
+ onsTable({
4
+ "caption": "Table with cells merged, multiple row headers and column headers",
5
+ "id": "basic-table",
6
+ "ths": [
7
+ {
8
+ "value": "Trade Category"
9
+ },
10
+ {
11
+ "value": "Metrics"
12
+ },
13
+ {
14
+ "value": "Exports"
15
+ },
16
+ {
17
+ "value": "Imports"
18
+ },
19
+ {
20
+ "value": "Balance"
21
+ }
22
+
23
+ ],
24
+ "trs": [
25
+ {
26
+ "tds": [
27
+ {
28
+ "heading": true,
29
+ "value": "Total trade in goods: December 2024 vs November 2024",
30
+ "rowspan":3,
31
+ "valign": "center"
32
+ },
33
+ {
34
+ "heading": true,
35
+ "value": "Value (£bn)"
36
+ },
37
+ {
38
+ "value": "28.8"
39
+ },
40
+ {
41
+ "value": "45.7"
42
+ },
43
+ {
44
+ "value": "-16.9"
45
+ }
46
+ ]
47
+ },
48
+ {
49
+ "tds": [
50
+ {
51
+ "heading": true,
52
+ "value": "Change (£bn)"
53
+ },
54
+ {
55
+ "value": "0.0"
56
+ },
57
+ {
58
+ "value": "-1.2"
59
+ },
60
+ {
61
+ "value": "1.2"
62
+ }
63
+ ]
64
+ },
65
+ {
66
+ "tds": [
67
+ {
68
+ "heading": true,
69
+ "value": "%Change"
70
+ },
71
+ {
72
+ "value": "0.0"
73
+ },
74
+ {
75
+ "value": "2.6"
76
+ },
77
+ {
78
+ "value": ""
79
+ }
80
+ ]
81
+ },
82
+ {
83
+ "tds": [
84
+ {
85
+ "heading": true,
86
+ "value": "EU: December 2024 vs November 2024",
87
+ "rowspan": 3,
88
+ "valign": "center"
89
+ },
90
+ {
91
+ "value": "Value (£bn)",
92
+ "heading": true
93
+ },
94
+ {
95
+ "value": "13.6"
96
+ },
97
+ {
98
+ "value": "24.9"
99
+ },
100
+ {
101
+ "value": "-11.3"
102
+ }
103
+ ]
104
+ },
105
+ {
106
+ "tds": [
107
+ {
108
+ "value": "Change (£bn)",
109
+ "heading": true
110
+ },
111
+ {
112
+ "value": "-0.5"
113
+
114
+ },
115
+ {
116
+ "value": "-0.8"
117
+ },
118
+ {
119
+ "value": "0.3"
120
+ }
121
+ ]
122
+ },
123
+ {
124
+ "tds": [
125
+ {
126
+ "heading": true,
127
+ "value": "%Change"
128
+ },
129
+ {
130
+ "value": "3.5"
131
+ },
132
+ {
133
+ "value": "-1.8"
134
+ },
135
+ {
136
+ "value": ""
137
+ }
138
+ ]
139
+ }
140
+ ]
141
+ })
142
+ }}