@ons/design-system 72.10.8 → 72.10.10

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 (66) hide show
  1. package/README.md +9 -5
  2. package/components/accordion/accordion.js +3 -2
  3. package/components/announcement-banner/_announcement-banner.scss +24 -0
  4. package/components/announcement-banner/_macro.njk +33 -0
  5. package/components/announcement-banner/_macro.spec.js +106 -0
  6. package/components/announcement-banner/_test_examples.js +22 -0
  7. package/components/announcement-banner/example-banner-black.njk +12 -0
  8. package/components/announcement-banner/example-banner-red.njk +13 -0
  9. package/components/announcement-banner/example-banner-teal.njk +13 -0
  10. package/components/autosuggest/autosuggest.spec.js +2 -0
  11. package/components/autosuggest/autosuggest.ui.js +12 -7
  12. package/components/breadcrumbs/_breadcrumbs.scss +53 -0
  13. package/components/breadcrumbs/_macro.njk +33 -24
  14. package/components/breadcrumbs/_macro.spec.js +25 -0
  15. package/components/button/_button.scss +29 -1
  16. package/components/chart/_chart.scss +88 -0
  17. package/components/chart/_macro.njk +25 -6
  18. package/components/chart/_macro.spec.js +1178 -640
  19. package/components/chart/bar-chart.js +8 -1
  20. package/components/chart/chart-iframe-resize.js +2 -2
  21. package/components/chart/common-chart-options.js +9 -0
  22. package/components/chart/example-bar-chart-with-axis-min-and-max-values.njk +0 -1
  23. package/components/chart/example-bar-chart-with-point-range-and-reference-line-annotations.njk +4 -4
  24. package/components/chart/example-bar-chart.njk +0 -1
  25. package/components/chart/example-iframe-chart.njk +1 -1
  26. package/components/chart/range-annotations-options.js +1 -1
  27. package/components/download-resources/download-resources.spec.js +2 -0
  28. package/components/duration/example-duration-error-for-single-field.njk +0 -1
  29. package/components/duration/example-duration-error.njk +0 -1
  30. package/components/footer/_macro.spec.js +2 -2
  31. package/components/header/_macro.njk +5 -16
  32. package/components/header/example-header-button-and-navigation.njk +133 -0
  33. package/components/header/example-header-external-with-navigation-and-search.njk +1 -1
  34. package/components/hero/_hero.scss +17 -22
  35. package/components/hero/_macro.njk +1 -1
  36. package/components/hero/_macro.spec.js +1 -1
  37. package/components/hero/example-hero-dark-with-external-breadcrumbs.njk +194 -0
  38. package/components/hero/example-hero-default-with-external-breadcrumbs.njk +201 -0
  39. package/components/hero/example-hero-grey-with-external-breadcrumbs.njk +243 -0
  40. package/components/hero/example-hero-navy-blue-with-external-breadcrumbs.njk +200 -0
  41. package/components/hero/example-hero-pale-blue-with-external-breadcrumbs.njk +201 -0
  42. package/components/icon/_macro.njk +1 -1
  43. package/components/mutually-exclusive/mutually-exclusive.js +3 -1
  44. package/components/navigation/_macro.njk +11 -16
  45. package/components/navigation/_navigation.scss +24 -0
  46. package/components/radios/clear-radios.js +4 -2
  47. package/components/relationships/relationships.js +4 -2
  48. package/components/table/_macro.njk +107 -112
  49. package/components/table/_macro.spec.js +35 -44
  50. package/components/table/_table.scss +0 -12
  51. package/components/table/example-table-sortable.njk +1 -1
  52. package/components/tabs/example-tabs-details.njk +1 -1
  53. package/components/textarea/_macro.njk +1 -0
  54. package/components/textarea/_macro.spec.js +1 -0
  55. package/components/timeout-panel/timeout-panel.spec.js +1 -1
  56. package/components/video/video.js +2 -0
  57. package/css/main.css +1 -1
  58. package/js/timeout.js +9 -6
  59. package/layout/_template.njk +13 -0
  60. package/package.json +6 -6
  61. package/scripts/main.es5.js +4 -2
  62. package/scripts/main.js +4 -2
  63. package/scss/main.scss +1 -0
  64. package/scss/vars/_colors.scss +3 -0
  65. package/scss/vars/_forms.scss +11 -0
  66. package/components/table/example-table-scrollable.njk +0 -158
package/README.md CHANGED
@@ -14,26 +14,30 @@ Nunjucks macros for components and templates are available from npm. Built CSS a
14
14
  yarn add @ons/design-system
15
15
  ```
16
16
 
17
- ## Run Locally
17
+ ## Running the DS Locally
18
18
 
19
- You'll need [Git](https://help.github.com/articles/set-up-git/), [Node.js](https://nodejs.org/en/), and [Yarn](https://yarnpkg.com/en/docs/getting-started) to run this project locally.
19
+ You'll need to install:
20
+
21
+ - [Git](https://help.github.com/articles/set-up-git/)
22
+ - [Node.js](https://nodejs.org/en/)
23
+ - [Yarn](https://yarnpkg.com/en/docs/getting-started) (When installing yarn be sure to install yarn through brew using `brew install yarn` not using npm)
20
24
 
21
25
  The version of node required is outlined in [.nvmrc](./.nvmrc).
22
26
 
23
- ### Using nvm (optional)
27
+ ### Using NVM
24
28
 
25
29
  If you work across multiple Node.js projects there's a good chance they require different Node.js and npm versions.
26
30
 
27
31
  To enable this we use [nvm (Node Version Manager)](https://github.com/creationix/nvm) to switch between versions easily.
28
32
 
29
33
  1. [install nvm](https://github.com/creationix/nvm#installation)
30
- 2. Run nvm install in the project directory (this will use .nvmrc)
34
+ 2. Run `nvm install` in the project directory (this will use .nvmrc)
31
35
 
32
36
  ### Install dependencies
33
37
 
34
38
  ```bash
35
39
  yarn install
36
- yarn husky install
40
+ yarn husky
37
41
  ```
38
42
 
39
43
  ### Start a local server
@@ -1,3 +1,4 @@
1
+ import DOMPurify from 'dompurify';
1
2
  export default class Accordion {
2
3
  constructor(button, detailsEls) {
3
4
  this.openDetailsEls = 0;
@@ -52,11 +53,11 @@ export default class Accordion {
52
53
 
53
54
  setButton() {
54
55
  if (this.canClose()) {
55
- this.buttonInner.innerHTML = this.closeButton;
56
+ this.buttonInner.innerHTML = DOMPurify.sanitize(this.closeButton);
56
57
  this.button.setAttribute('data-ga-label', this.buttonOpen);
57
58
  this.button.setAttribute('aria-expanded', 'true');
58
59
  } else {
59
- this.buttonInner.innerHTML = this.buttonOpen;
60
+ this.buttonInner.innerHTML = DOMPurify.sanitize(this.buttonOpen);
60
61
  this.button.setAttribute('data-ga-label', this.closeButton);
61
62
  this.button.setAttribute('aria-expanded', 'false');
62
63
  }
@@ -0,0 +1,24 @@
1
+ .ons-announcement-banner {
2
+ color: var(--ons-color-text-inverse);
3
+ padding: 1rem;
4
+ &--black {
5
+ background-color: var(--ons-color-gov-black);
6
+ }
7
+
8
+ &--teal {
9
+ background-color: var(--ons-color-gov-teal);
10
+ }
11
+
12
+ &--red {
13
+ background-color: var(--ons-color-gov-red);
14
+ }
15
+
16
+ &__link {
17
+ color: var(--ons-color-text-inverse);
18
+
19
+ &:hover {
20
+ color: var(--ons-color-text-inverse);
21
+ text-decoration: underline solid var(--ons-color-text-inverse) 2px;
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,33 @@
1
+ {% macro onsAnnouncementBanner(params) %}
2
+
3
+ {% if params.variants %}
4
+ {% if 'wide' in params.variants %}
5
+ {% set width = "wide" %}
6
+ {% endif %}
7
+ {% endif %}
8
+
9
+ {% set content %}
10
+ <div
11
+ class="ons-announcement-banner{% if params.variants %}{% if params.variants == 'wide' or params.variants | length == 1 and 'wide' in params.variants %}{{ ' ' }}ons-announcement-banner--black{% elif params.variants is not string %}{% for variant in params.variants %}{{ ' ' }}ons-announcement-banner--{{ variant }}{% endfor %}{% else %}{{ ' ' }}ons-announcement-banner--{{ params.variants }}{% endif %}{% else %}{{ ' ' }}ons-announcement-banner--black{% endif %}"
12
+ >
13
+ <h2 class="ons-announcement-banner__title">{{ params.title }}</h2>
14
+ <p class="ons-announcement-banner__description">{{ params.description }}</p>
15
+ <a
16
+ class="ons-announcement-banner__link"
17
+ {% if params.link.attributes %}{% for attribute, value in (params.link.attributes.items() if params.link.attributes is mapping and params.link.attributes.items else params.link.attributes) %}{{ ' ' }}{{ attribute }}="{{ value }}"{% endfor %}{% endif %}
18
+ href="{{ params.link.url }}"
19
+ >{{ params.link.text }}</a
20
+ >
21
+ </div>
22
+ {% endset %}
23
+
24
+ {% if width != "wide" %}
25
+ <div
26
+ class="ons-announcement-banner__container{% if params.variants %}{% if params.variants is not string %}{% for variant in params.variants %}{{ ' ' }}ons-announcement-banner--{{ variant }}{% endfor %}{% else %}{{ ' ' }}ons-announcement-banner--{{ params.variants }}{% endif %}{% else %}{{ ' ' }}ons-announcement-banner--black{% endif %}"
27
+ >
28
+ <div class="ons-container">{{ content | safe }}</div>
29
+ </div>
30
+ {% else %}
31
+ {{ content | safe }}
32
+ {% endif %}
33
+ {% endmacro %}
@@ -0,0 +1,106 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as cheerio from 'cheerio';
4
+
5
+ import axe from '../../tests/helpers/axe';
6
+ import { renderComponent } from '../../tests/helpers/rendering';
7
+ import { EXAMPLE_FULL_ANNOUNCEMENT_BANNER, EXAMPLE_REQUIRED_ANNOUNCEMENT_BANNER } from './_test_examples';
8
+
9
+ describe('FOR: Macro: Announcement-banner', () => {
10
+ describe('GIVEN: Params: required', () => {
11
+ describe('WHEN: all required params are provided', () => {
12
+ const $ = cheerio.load(renderComponent('announcement-banner', EXAMPLE_REQUIRED_ANNOUNCEMENT_BANNER));
13
+ test('THEN: jest-axe checks pass', async () => {
14
+ const results = await axe($.html());
15
+ expect(results).toHaveNoViolations();
16
+ });
17
+
18
+ test('THEN: the title has the provided text', async () => {
19
+ const title = $('.ons-announcement-banner__title');
20
+ expect(title.text().trim()).toBe('This is a black banner');
21
+ });
22
+
23
+ test('THEN: the description has the provided text', async () => {
24
+ const description = $('.ons-announcement-banner__description');
25
+ expect(description.text().trim()).toBe('This is a description for the black banner');
26
+ });
27
+
28
+ test('THEN: the link points to the provided URL', async () => {
29
+ const linkUrl = $('.ons-announcement-banner__link');
30
+ expect(linkUrl.attr('href')).toBe('http://example.com');
31
+ });
32
+
33
+ test('THEN: the link has the provided link text', async () => {
34
+ const linkText = $('.ons-announcement-banner__link');
35
+ expect(linkText.text().trim()).toBe('Find out more');
36
+ });
37
+
38
+ test('THEN: it defaults to the black variant', async () => {
39
+ const banner = $('.ons-announcement-banner');
40
+ expect(banner.hasClass('ons-announcement-banner--black')).toBe(true);
41
+ });
42
+
43
+ test('THEN: containers are created', () => {
44
+ expect($('.ons-announcement-banner--black > .ons-container').length).toBe(1);
45
+ });
46
+ });
47
+ });
48
+
49
+ describe('GIVEN: Params: link attributes', () => {
50
+ const $ = cheerio.load(renderComponent('announcement-banner', EXAMPLE_FULL_ANNOUNCEMENT_BANNER));
51
+ describe('WHEN: link attributes are provided', () => {
52
+ test('THEN: the link has the provided attributes', async () => {
53
+ const link = $('.ons-announcement-banner__link');
54
+ expect(link.attr('abc')).toBe('123');
55
+ expect(link.attr('def')).toBe('456');
56
+ });
57
+ });
58
+ });
59
+
60
+ describe('GIVEN: Params: variants', () => {
61
+ describe('WHEN: variants is provided as a string', () => {
62
+ const $ = cheerio.load(renderComponent('announcement-banner', EXAMPLE_FULL_ANNOUNCEMENT_BANNER));
63
+ test('THEN: the banner has the correct variant class', async () => {
64
+ const banner = $('.ons-announcement-banner');
65
+ expect(banner.hasClass('ons-announcement-banner--red')).toBe(true);
66
+ });
67
+ });
68
+ describe('WHEN: wide is provided as one of the variants', () => {
69
+ const $ = cheerio.load(
70
+ renderComponent('announcement-banner', { ...EXAMPLE_REQUIRED_ANNOUNCEMENT_BANNER, variants: ['wide', 'red'] }),
71
+ );
72
+ test('THEN: containers are not created with the correct classes', () => {
73
+ expect($('.ons-announcement-banner--red > .ons-container').length).toBe(0);
74
+ });
75
+ });
76
+ describe('WHEN: red is provided alongside other variants', () => {
77
+ const $ = cheerio.load(
78
+ renderComponent('announcement-banner', { ...EXAMPLE_REQUIRED_ANNOUNCEMENT_BANNER, variants: ['wide', 'red'] }),
79
+ );
80
+ test('THEN: the banner has the correct variant class', async () => {
81
+ const banner = $('.ons-announcement-banner');
82
+ expect(banner.hasClass('ons-announcement-banner--red')).toBe(true);
83
+ });
84
+ });
85
+ describe('WHEN: wide is provided as the only variant as a string', () => {
86
+ const $ = cheerio.load(renderComponent('announcement-banner', { ...EXAMPLE_REQUIRED_ANNOUNCEMENT_BANNER, variants: 'wide' }));
87
+ test('THEN: it defaults to the black variant', async () => {
88
+ const banner = $('.ons-announcement-banner');
89
+ expect(banner.hasClass('ons-announcement-banner--black')).toBe(true);
90
+ });
91
+ test('THEN: containers are not created with the correct classes', () => {
92
+ expect($('.ons-announcement-banner--black > .ons-container').length).toBe(0);
93
+ });
94
+ });
95
+ describe('WHEN: wide is provided as the only variant in an array', () => {
96
+ const $ = cheerio.load(renderComponent('announcement-banner', { ...EXAMPLE_REQUIRED_ANNOUNCEMENT_BANNER, variants: ['wide'] }));
97
+ test('THEN: it defaults to the black variant', async () => {
98
+ const banner = $('.ons-announcement-banner');
99
+ expect(banner.hasClass('ons-announcement-banner--black')).toBe(true);
100
+ });
101
+ test('THEN: containers are not created with the correct classes', () => {
102
+ expect($('.ons-announcement-banner--black > .ons-container').length).toBe(0);
103
+ });
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,22 @@
1
+ export const EXAMPLE_FULL_ANNOUNCEMENT_BANNER = {
2
+ variants: 'red',
3
+ title: 'This is a red banner',
4
+ description: 'This is a description for the red banner',
5
+ link: {
6
+ text: 'Find out more',
7
+ url: 'http://example.com',
8
+ attributes: {
9
+ abc: '123',
10
+ def: '456',
11
+ },
12
+ },
13
+ };
14
+
15
+ export const EXAMPLE_REQUIRED_ANNOUNCEMENT_BANNER = {
16
+ title: 'This is a black banner',
17
+ description: 'This is a description for the black banner',
18
+ link: {
19
+ text: 'Find out more',
20
+ url: 'http://example.com',
21
+ },
22
+ };
@@ -0,0 +1,12 @@
1
+ {% from "components/announcement-banner/_macro.njk" import onsAnnouncementBanner %}
2
+
3
+ {{
4
+ onsAnnouncementBanner({
5
+ "title": 'His Royal Highness Henry VIII',
6
+ "description": '1491 to 1547',
7
+ "link": {
8
+ "text": 'Find out more',
9
+ "url": '#0'
10
+ }
11
+ })
12
+ }}
@@ -0,0 +1,13 @@
1
+ {% from "components/announcement-banner/_macro.njk" import onsAnnouncementBanner %}
2
+
3
+ {{
4
+ onsAnnouncementBanner({
5
+ "variants": ['red', 'wide'],
6
+ "title": 'National emergency',
7
+ "description": 'This is a level 1 incident',
8
+ "link": {
9
+ "text": 'More information',
10
+ "url": '#0'
11
+ }
12
+ })
13
+ }}
@@ -0,0 +1,13 @@
1
+ {% from "components/announcement-banner/_macro.njk" import onsAnnouncementBanner %}
2
+
3
+ {{
4
+ onsAnnouncementBanner({
5
+ "variants": 'teal',
6
+ "title": 'Local emergency',
7
+ "description": 'This is a level 2 incident',
8
+ "link": {
9
+ "text": 'More information',
10
+ "url": '#0'
11
+ }
12
+ })
13
+ }}
@@ -386,9 +386,11 @@ describe('script: autosuggest', () => {
386
386
  describe('when the mouse moves over a result and a suggestion is focused', () => {
387
387
  it('removes the focused class', async () => {
388
388
  await setTestPage('/test', renderComponent('autosuggest', EXAMPLE_AUTOSUGGEST));
389
+ await page.mouse.move(0, 0); // move out of the component
389
390
 
390
391
  await page.type('.ons-js-autosuggest-input', 'state', { delay: 20 });
391
392
  await page.keyboard.press('ArrowDown');
393
+
392
394
  await page.hover('.ons-autosuggest__option:nth-child(2)');
393
395
 
394
396
  const focusedClassCount = await page.$$eval('.ons-autosuggest__option--focused', (nodes) => nodes.length);
@@ -1,6 +1,7 @@
1
1
  import abortableFetch from '../../js/abortable-fetch';
2
2
  import { sanitiseAutosuggestText } from './autosuggest.helpers';
3
3
  import runFuse from './fuse-config';
4
+ import DOMPurify from 'dompurify';
4
5
 
5
6
  export const baseClass = 'ons-js-autosuggest';
6
7
 
@@ -398,7 +399,7 @@ export default class AutosuggestUI {
398
399
  const listElement = document.createElement('li');
399
400
  listElement.className = `${classAutosuggestOption} ${classAutosuggestOptionMoreResults}`;
400
401
  listElement.setAttribute('aria-hidden', 'true');
401
- listElement.innerHTML = this.moreResults;
402
+ listElement.innerHTML = DOMPurify.sanitize(this.moreResults);
402
403
  this.listbox.appendChild(listElement);
403
404
  }
404
405
 
@@ -436,10 +437,12 @@ export default class AutosuggestUI {
436
437
  if (status === 400 || status === false) {
437
438
  message = this.typeMore;
438
439
  this.setAriaStatus(message);
439
- this.listbox.innerHTML = `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`;
440
+ this.listbox.innerHTML = DOMPurify.sanitize(
441
+ `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`,
442
+ );
440
443
  } else if (status > 400 || status === '') {
441
- message =
442
- this.errorAPI + (this.errorAPILinkText ? ' <a href="' + window.location.href + '">' + this.errorAPILinkText + '</a>.' : '');
444
+ const sanitizedHref = DOMPurify.sanitize(window.location.href);
445
+ message = this.errorAPI + (this.errorAPILinkText ? ' <a href="' + sanitizedHref + '">' + this.errorAPILinkText + '</a>.' : '');
443
446
  let ariaMessage = this.errorAPI + (this.errorAPILinkText ? ' ' + this.errorAPILinkText : '');
444
447
 
445
448
  this.input.setAttribute('disabled', true);
@@ -453,7 +456,9 @@ export default class AutosuggestUI {
453
456
  this.resultsTitleContainer.remove();
454
457
  } else {
455
458
  message = this.noResults;
456
- this.listbox.innerHTML = `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`;
459
+ this.listbox.innerHTML = DOMPurify.sanitize(
460
+ `<li class="${classAutosuggestOption} ${classAutosuggestOptionNoResults}">${message}</li>`,
461
+ );
457
462
  }
458
463
  }
459
464
 
@@ -503,7 +508,7 @@ export default class AutosuggestUI {
503
508
  }
504
509
  }
505
510
  }
506
- this.ariaStatus.innerHTML = content;
511
+ this.ariaStatus.innerHTML = DOMPurify.sanitize(content);
507
512
  }
508
513
 
509
514
  selectResult(index) {
@@ -548,7 +553,7 @@ export default class AutosuggestUI {
548
553
  warningSpanElement.innerHTML = '!';
549
554
 
550
555
  warningBodyElement.className = 'ons-panel__body';
551
- warningBodyElement.innerHTML = content;
556
+ warningBodyElement.innerHTML = DOMPurify.sanitize(content);
552
557
 
553
558
  warningElement.appendChild(warningSpanElement);
554
559
  warningElement.appendChild(warningBodyElement);
@@ -1,5 +1,58 @@
1
1
  $breadcrumb-chevron-height: 0.65rem;
2
2
 
3
+ .ons-breadcrumbs-wrapper {
4
+ position: relative;
5
+ z-index: 10;
6
+ margin-bottom: -2rem;
7
+ &--grey {
8
+ background-color: var(--ons-color-banner-bg);
9
+ }
10
+
11
+ &--dark {
12
+ background-color: var(--ons-color-hero-bg-dark);
13
+ }
14
+
15
+ &--navy-blue {
16
+ background-color: var(--ons-color-navy-blue-light);
17
+ margin-bottom: -1.75rem;
18
+ @include mq(l) {
19
+ margin-bottom: -3.25rem;
20
+ }
21
+ }
22
+
23
+ &--dark,
24
+ &--navy-blue {
25
+ .ons-icon {
26
+ color: var(--ons-color-text-inverse);
27
+ }
28
+
29
+ .ons-breadcrumbs__link {
30
+ color: var(--ons-color-text-inverse);
31
+ text-decoration: underline;
32
+
33
+ &:hover {
34
+ color: var(--ons-color-text-inverse);
35
+ text-decoration: underline solid var(--ons-color-text-inverse) 2px;
36
+ }
37
+
38
+ &:focus {
39
+ color: var(--ons-color-text);
40
+ }
41
+
42
+ &:focus:hover {
43
+ text-decoration: none;
44
+ }
45
+ }
46
+ }
47
+
48
+ &--pale-blue {
49
+ background-color: var(--ons-color-pale-blue);
50
+ }
51
+ .ons-breadcrumbs {
52
+ padding: 1rem;
53
+ }
54
+ }
55
+
3
56
  .ons-breadcrumbs {
4
57
  align-items: center;
5
58
  display: flex;
@@ -1,28 +1,37 @@
1
1
  {% from "components/icon/_macro.njk" import onsIcon %}
2
-
3
2
  {% macro onsBreadcrumbs(params) %}
4
- <nav
5
- class="ons-breadcrumbs{{ ' ' + params.classes if params.classes else '' }}"
6
- aria-label="{{ params.ariaLabel | default('Breadcrumbs') }}"
7
- {% if params.id %}id="{{ params.id }}"{% endif %}
8
- >
9
- <ol class="ons-breadcrumbs__items ons-u-fs-s">
10
- {%- for item in params.itemsList -%}
11
- <li class="ons-breadcrumbs__item{{ ' ' + item.itemClasses if item.itemClasses else '' }}" id="breadcrumb-{{ loop.index }}">
12
- <a
13
- class="ons-breadcrumbs__link{{ ' ' + item.linkClasses if item.linkClasses else '' }}"
14
- href="{% if not isDesignSystemExample %}{{ item.url }}{% else %}#0{% endif %}"
15
- {% if item.id %}{{ ' ' }}id='{{ item.id }}'{% endif %}
16
- {% if item.attributes %}{% for attribute, value in (item.attributes.items() if item.attributes is mapping and item.attributes.items else item.attributes) %}{{ ' ' }}{{ attribute }}="{{ value }}"{% endfor %}{% endif %}
17
- >{{ item.text }}</a
3
+ {% set breadcrumbs %}
4
+ <nav
5
+ class="ons-breadcrumbs{{ ' ons-container' if params.variant }}{{ ' ' + params.classes if params.classes else '' }}"
6
+ aria-label="{{ params.ariaLabel | default('Breadcrumbs') }}"
7
+ {% if params.id %}id="{{ params.id }}"{% endif %}
8
+ >
9
+ <ol class="ons-breadcrumbs__items ons-u-fs-s">
10
+ {%- for item in params.itemsList -%}
11
+ <li
12
+ class="ons-breadcrumbs__item{{ ' ' + item.itemClasses if item.itemClasses else '' }}"
13
+ id="breadcrumb-{{ loop.index }}"
18
14
  >
19
- {{-
20
- onsIcon({
21
- "iconType": "chevron"
22
- })
23
- -}}
24
- </li>
25
- {%- endfor -%}
26
- </ol>
27
- </nav>
15
+ <a
16
+ class="ons-breadcrumbs__link{{ ' ' + item.linkClasses if item.linkClasses else '' }}"
17
+ href="{% if not isDesignSystemExample %}{{ item.url }}{% else %}#0{% endif %}"
18
+ {% if item.id %}{{ ' ' }}id='{{ item.id }}'{% endif %}
19
+ {% if item.attributes %}{% for attribute, value in (item.attributes.items() if item.attributes is mapping and item.attributes.items else item.attributes) %}{{ ' ' }}{{ attribute }}="{{ value }}"{% endfor %}{% endif %}
20
+ >{{ item.text }}</a
21
+ >
22
+ {{-
23
+ onsIcon({
24
+ "iconType": "chevron"
25
+ })
26
+ -}}
27
+ </li>
28
+ {%- endfor -%}
29
+ </ol>
30
+ </nav>
31
+ {% endset %}
32
+ {% if params.variant %}
33
+ <div class="ons-breadcrumbs-wrapper ons-breadcrumbs-wrapper--{{ params.variant }}">{{ breadcrumbs | safe }}</div>
34
+ {% else %}
35
+ {{ breadcrumbs | safe }}
36
+ {% endif %}
28
37
  {% endmacro %}
@@ -132,4 +132,29 @@ describe('FOR: Macro: Breadcrumbs', () => {
132
132
  });
133
133
  });
134
134
  });
135
+
136
+ describe('GIVEN: Params: variant', () => {
137
+ describe('WHEN: variant is provided', () => {
138
+ const $ = cheerio.load(
139
+ renderComponent('breadcrumbs', {
140
+ ...EXAMPLE_BREADCRUMBS_REQUIRED_PARAMS,
141
+ variant: 'grey',
142
+ }),
143
+ );
144
+ test('THEN: renders breadcrumbs in a breadcrumbs-wrapper with correct modifier class', () => {
145
+ expect($('.ons-breadcrumbs-wrapper').hasClass('ons-breadcrumbs-wrapper--grey')).toBe(true);
146
+ });
147
+ });
148
+
149
+ describe('WHEN: variant is not provided', () => {
150
+ const $ = cheerio.load(
151
+ renderComponent('breadcrumbs', {
152
+ ...EXAMPLE_BREADCRUMBS_REQUIRED_PARAMS,
153
+ }),
154
+ );
155
+ test('THEN: does not render breadcrumbs-wrapper', () => {
156
+ expect($('.ons-breadcrumbs-wrapper').length).toBe(0);
157
+ });
158
+ });
159
+ });
135
160
  });
@@ -99,7 +99,7 @@ $button-shadow-size: 3px;
99
99
  }
100
100
  }
101
101
 
102
- // When preceded by another button (for example, in a group)
102
+ // When preceded by another button (for example in a group)
103
103
  & + & {
104
104
  margin-left: 0.5rem;
105
105
  }
@@ -576,6 +576,34 @@ $button-shadow-size: 3px;
576
576
  }
577
577
  }
578
578
 
579
+ .ons-navigation--neutral &--dropdown {
580
+ .ons-btn__inner {
581
+ background: var(--ons-color-grey-15);
582
+ color: var(--ons-color-black);
583
+ .ons-icon {
584
+ fill: var(--ons-color-black);
585
+ }
586
+ }
587
+ &:focus {
588
+ .ons-btn__inner {
589
+ background: var(--ons-color-focus);
590
+ }
591
+ }
592
+ }
593
+
594
+ .ons-navigation--neutral &--dropdown:active &,
595
+ .ons-navigation--neutral &--dropdown.active &,
596
+ .ons-navigation--neutral &--dropdown:active:focus &,
597
+ .ons-navigation--neutral &--dropdown.active:focus & {
598
+ &__inner {
599
+ background: var(--ons-color-grey-100);
600
+ color: var(--ons-color-white);
601
+ .ons-icon {
602
+ fill: var(--ons-color-white);
603
+ }
604
+ }
605
+ }
606
+
579
607
  &--neutral &,
580
608
  &--neutral:hover &,
581
609
  &--neutral:active &,