@ons/design-system 72.1.2 → 72.2.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.
- package/components/char-check-limit/_macro.spec.js +50 -58
- package/components/description-list/_macro.njk +7 -1
- package/components/description-list/_macro.spec.js +4 -1
- package/components/description-list/example-inline-description-list.njk +2 -1
- package/components/hero/_hero.scss +45 -4
- package/components/hero/_macro.njk +49 -10
- package/components/hero/_macro.spec.js +84 -1
- package/components/hero/example-hero-grey.njk +78 -0
- package/components/icon/_macro.njk +146 -0
- package/components/list/_list.scss +3 -0
- package/components/list/_macro.njk +12 -18
- package/components/list/_macro.spec.js +11 -7
- package/components/panel/_macro.njk +1 -1
- package/components/panel/_macro.spec.js +15 -1
- package/css/main.css +1 -1
- package/package.json +1 -1
- package/scss/vars/_colors.scss +1 -0
|
@@ -5,65 +5,57 @@ import * as cheerio from 'cheerio';
|
|
|
5
5
|
import axe from '../../tests/helpers/axe';
|
|
6
6
|
import { renderComponent } from '../../tests/helpers/rendering';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const results = await axe($.html());
|
|
37
|
-
expect(results).toHaveNoViolations();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('has the provided `id` attribute', () => {
|
|
41
|
-
const $ = cheerio.load(renderComponent('char-check-limit', EXAMPLE_CHAR_CHECK_LIMIT));
|
|
42
|
-
|
|
43
|
-
expect($('.ons-input__limit').attr('id')).toBe('example-char-check-limit');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('has the provided data attributes', () => {
|
|
47
|
-
const $ = cheerio.load(renderComponent('char-check-limit', EXAMPLE_CHAR_CHECK_LIMIT));
|
|
48
|
-
|
|
49
|
-
expect($('.ons-input__limit').attr('data-charcount-singular')).toBe('You have {x} character remaining');
|
|
50
|
-
expect($('.ons-input__limit').attr('data-charcount-plural')).toBe('You have {x} characters remaining');
|
|
51
|
-
expect($('.ons-input__limit').attr('data-charcount-limit-singular')).toBe('{x} character too many');
|
|
52
|
-
expect($('.ons-input__limit').attr('data-charcount-limit-plural')).toBe('{x} characters too many');
|
|
8
|
+
import { EXAMPLE_CHAR_CHECK_LIMIT } from './_test-examples';
|
|
9
|
+
|
|
10
|
+
describe('FOR: Macro: CharCheckLimit', () => {
|
|
11
|
+
describe('GIVEN: Params: Required', () => {
|
|
12
|
+
describe('WHEN: required params are provided', () => {
|
|
13
|
+
const $ = cheerio.load(renderComponent('char-check-limit', EXAMPLE_CHAR_CHECK_LIMIT));
|
|
14
|
+
|
|
15
|
+
test('THEN: passes jest-axe checks', async () => {
|
|
16
|
+
const results = await axe($.html());
|
|
17
|
+
|
|
18
|
+
expect(results).toHaveNoViolations();
|
|
19
|
+
});
|
|
20
|
+
test('THEN: has the provided id attribute', () => {
|
|
21
|
+
expect($('.ons-input__limit').attr('id')).toBe('example-char-check-limit');
|
|
22
|
+
});
|
|
23
|
+
test('THEN: has the data attribute which defines charCountPlural', () => {
|
|
24
|
+
expect($('.ons-input__limit').attr('data-charcount-plural')).toBe('You have {x} characters remaining');
|
|
25
|
+
});
|
|
26
|
+
test('THEN: has the data attribute which defines charCountSingular', () => {
|
|
27
|
+
expect($('.ons-input__limit').attr('data-charcount-singular')).toBe('You have {x} character remaining');
|
|
28
|
+
});
|
|
29
|
+
test('THEN: has the data attribute which defines charCountOverLimitSingular', () => {
|
|
30
|
+
expect($('.ons-input__limit').attr('data-charcount-limit-singular')).toBe('{x} character too many');
|
|
31
|
+
});
|
|
32
|
+
test('THEN: has the data attribute which defines charCountOverLimitPlural', () => {
|
|
33
|
+
expect($('.ons-input__limit').attr('data-charcount-limit-plural')).toBe('{x} characters too many');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
53
36
|
});
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
38
|
+
describe('GIVEN: Params: variant', () => {
|
|
39
|
+
describe('WHEN: variant is provided', () => {
|
|
40
|
+
const $ = cheerio.load(
|
|
41
|
+
renderComponent(
|
|
42
|
+
'char-check-limit',
|
|
43
|
+
{
|
|
44
|
+
...EXAMPLE_CHAR_CHECK_LIMIT,
|
|
45
|
+
variant: 'check',
|
|
46
|
+
},
|
|
47
|
+
['<p>Test content.</p>'],
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
test('THEN: passes jest-axe checks with variant set to check', async () => {
|
|
52
|
+
const results = await axe($.html());
|
|
53
|
+
expect(results).toHaveNoViolations();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('THEN: renders the passed content', () => {
|
|
57
|
+
expect($('.ons-js-char-check-input').text()).toBe('Test content.');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
68
60
|
});
|
|
69
61
|
});
|
|
@@ -21,7 +21,13 @@
|
|
|
21
21
|
{% if descriptionItem.id %}id="{{ descriptionItem.id }}"{% endif %}
|
|
22
22
|
class="ons-description-list__value ons-grid__col ons-col-{{ params.descriptionCol }}@{{ 'xs@l' if params.variant == 'inline' else 'm' }}"
|
|
23
23
|
>
|
|
24
|
-
{
|
|
24
|
+
{%- if descriptionItem.url -%}
|
|
25
|
+
<a class="ons-description-list__link" href="{{ descriptionItem.url }}">
|
|
26
|
+
{{- descriptionItem.description -}}
|
|
27
|
+
</a>
|
|
28
|
+
{%- else -%}
|
|
29
|
+
{{- descriptionItem.description -}}
|
|
30
|
+
{%- endif -%}
|
|
25
31
|
</dd>
|
|
26
32
|
{% endif %}
|
|
27
33
|
{% endfor %}
|
|
@@ -30,6 +30,7 @@ const EXAMPLE_DESCRIPTION_LIST_FULL = {
|
|
|
30
30
|
{
|
|
31
31
|
id: 'description-3',
|
|
32
32
|
description: '49300005832',
|
|
33
|
+
url: '#',
|
|
33
34
|
},
|
|
34
35
|
],
|
|
35
36
|
},
|
|
@@ -142,7 +143,9 @@ describe('macro: description-list', () => {
|
|
|
142
143
|
|
|
143
144
|
expect($listElements[4].tagName).toBe('dd');
|
|
144
145
|
expect($($listElements[4]).attr('id')).toBe('description-3');
|
|
145
|
-
|
|
146
|
+
const $link = $($listElements[4]).find('a');
|
|
147
|
+
expect($link.attr('href')).toBe('#');
|
|
148
|
+
expect($link.text()).toBe('49300005832');
|
|
146
149
|
});
|
|
147
150
|
|
|
148
151
|
it.each([
|
|
@@ -28,6 +28,35 @@
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
&--grey {
|
|
32
|
+
background-color: var(--ons-color-grey-10);
|
|
33
|
+
&::before {
|
|
34
|
+
content: '';
|
|
35
|
+
background-color: var(--ons-color-banner-bg);
|
|
36
|
+
border-radius: 0 0 50% 50%;
|
|
37
|
+
inset: 0;
|
|
38
|
+
left: -40%;
|
|
39
|
+
position: absolute;
|
|
40
|
+
width: 150%;
|
|
41
|
+
@include mq(l) {
|
|
42
|
+
border-radius: 0 0 300% 150%;
|
|
43
|
+
left: 0;
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
&__badge {
|
|
49
|
+
@include mq(xs, l) {
|
|
50
|
+
margin-top: 2.5rem;
|
|
51
|
+
margin-bottom: 1rem;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&--topic {
|
|
56
|
+
color: var(--ons-color-branded);
|
|
57
|
+
@extend .ons-u-mb-no;
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
&__container {
|
|
32
61
|
align-items: center;
|
|
33
62
|
display: flex;
|
|
@@ -43,10 +72,6 @@
|
|
|
43
72
|
height: 100%;
|
|
44
73
|
}
|
|
45
74
|
|
|
46
|
-
&__text:has(+ .ons-btn) {
|
|
47
|
-
margin-bottom: 2rem;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
75
|
&__pre-title {
|
|
51
76
|
margin-bottom: 0.5rem;
|
|
52
77
|
|
|
@@ -62,6 +87,10 @@
|
|
|
62
87
|
z-index: 5;
|
|
63
88
|
}
|
|
64
89
|
|
|
90
|
+
&__details:has(.ons-breadcrumbs) {
|
|
91
|
+
padding-top: 1rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
65
94
|
&--dark & {
|
|
66
95
|
&__details {
|
|
67
96
|
color: var(--ons-color-text-inverse) !important;
|
|
@@ -299,4 +328,16 @@
|
|
|
299
328
|
}
|
|
300
329
|
}
|
|
301
330
|
}
|
|
331
|
+
|
|
332
|
+
&__title-container {
|
|
333
|
+
@include mq(l) {
|
|
334
|
+
display: grid;
|
|
335
|
+
align-items: start;
|
|
336
|
+
justify-content: space-between;
|
|
337
|
+
|
|
338
|
+
&.ons-hero__title-badge {
|
|
339
|
+
grid-template-columns: 1fr auto;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
302
343
|
}
|
|
@@ -15,16 +15,41 @@
|
|
|
15
15
|
{% endif %}
|
|
16
16
|
<div class="ons-hero__container ons-container{{ ' ons-container--wide' if params.wide }}">
|
|
17
17
|
<div class="ons-hero__details ons-grid__col ons-col-{{ detailsColumns }}@m ons-col-10@s@m">
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
{% if params.breadcrumbs %}
|
|
19
|
+
{% from "components/breadcrumbs/_macro.njk" import onsBreadcrumbs %}
|
|
20
|
+
{{
|
|
21
|
+
onsBreadcrumbs({
|
|
22
|
+
"itemsList": params.breadcrumbs.itemsList,
|
|
23
|
+
"ariaLabel": params.breadcrumbs.ariaLabel,
|
|
24
|
+
"id": params.breadcrumbs.id,
|
|
25
|
+
"classes": 'ons-u-pt-no' ~ (' ' + params.breadcrumbs.classes if params.breadcrumbs.classes else '')
|
|
26
|
+
})
|
|
27
|
+
}}
|
|
28
|
+
{% endif %}
|
|
29
|
+
{% if params.topic %}
|
|
30
|
+
<h2 class="ons-hero--topic">{{ params.topic | safe }}</h2>
|
|
27
31
|
{% endif %}
|
|
32
|
+
<div class="ons-hero__title-container {% if params.officialStatisticsBadge %}ons-hero__title-badge{% endif %}">
|
|
33
|
+
<header>
|
|
34
|
+
<h1 class="ons-hero__title ons-u-fs-3xl">{{ params.title }}</h1>
|
|
35
|
+
{% if params.subtitle %}
|
|
36
|
+
<h2 class="ons-hero__subtitle ons-u-fs-r--b">{{ params.subtitle }}</h2>
|
|
37
|
+
{% endif %}
|
|
38
|
+
</header>
|
|
39
|
+
{% if params.officialStatisticsBadge == true %}
|
|
40
|
+
{% from "components/icon/_macro.njk" import onsIcon %}
|
|
41
|
+
<div class="ons-hero__badge">
|
|
42
|
+
{{-
|
|
43
|
+
onsIcon({
|
|
44
|
+
"iconType": 'official-statistics'
|
|
45
|
+
})
|
|
46
|
+
-}}
|
|
47
|
+
</div>
|
|
48
|
+
{% endif %}
|
|
49
|
+
{% if params.text %}
|
|
50
|
+
<p class="ons-hero__text">{{ params.text | safe }}</p>
|
|
51
|
+
{% endif %}
|
|
52
|
+
</div>
|
|
28
53
|
|
|
29
54
|
{% if params.button %}
|
|
30
55
|
{% from "components/button/_macro.njk" import onsButton %}
|
|
@@ -34,7 +59,7 @@
|
|
|
34
59
|
{% endif %}
|
|
35
60
|
{{
|
|
36
61
|
onsButton({
|
|
37
|
-
"classes": btnClasses,
|
|
62
|
+
"classes": 'ons-u-mt-s' + btnClasses,
|
|
38
63
|
"type": 'button',
|
|
39
64
|
"text": params.button.text,
|
|
40
65
|
"url": params.button.url
|
|
@@ -44,7 +69,21 @@
|
|
|
44
69
|
{% if caller %}
|
|
45
70
|
<div class="ons-hero__additional-content">{{- caller() -}}</div>
|
|
46
71
|
{% endif %}
|
|
72
|
+
|
|
73
|
+
{% if params.descriptionList %}
|
|
74
|
+
{% from "components/description-list/_macro.njk" import onsDescriptionList %}
|
|
75
|
+
{{
|
|
76
|
+
onsDescriptionList({
|
|
77
|
+
"classes": "ons-u-mb-no",
|
|
78
|
+
"variant": 'inline',
|
|
79
|
+
"termCol": params.descriptionList.termCol,
|
|
80
|
+
"descriptionCol": params.descriptionList.descriptionCol,
|
|
81
|
+
"itemsList": params.descriptionList.itemsList
|
|
82
|
+
})
|
|
83
|
+
}}
|
|
84
|
+
{% endif %}
|
|
47
85
|
</div>
|
|
86
|
+
|
|
48
87
|
{% if params.html %}
|
|
49
88
|
<div class="ons-hero__additional-html">{{- params.html | safe -}}</div>
|
|
50
89
|
{% endif %}
|
|
@@ -78,7 +78,7 @@ describe('macro: hero', () => {
|
|
|
78
78
|
|
|
79
79
|
faker.renderComponent('hero', { ...EXAMPLE_HERO, variants: 'dark' });
|
|
80
80
|
|
|
81
|
-
expect(buttonSpy.occurrences[0]).toHaveProperty('classes', ' ons-btn--ghost');
|
|
81
|
+
expect(buttonSpy.occurrences[0]).toHaveProperty('classes', 'ons-u-mt-s ons-btn--ghost');
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
it('calls with content', () => {
|
|
@@ -92,4 +92,87 @@ describe('macro: hero', () => {
|
|
|
92
92
|
const $ = cheerio.load(renderComponent('hero', { ...EXAMPLE_HERO, variants: 'navy-blue' }));
|
|
93
93
|
expect($('.ons-hero--navy-blue .ons-hero__circles').length).toBe(1);
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
it('outputs the correct topic when set', () => {
|
|
97
|
+
const $ = cheerio.load(renderComponent('hero', { ...EXAMPLE_HERO, topic: 'Topic Text' }));
|
|
98
|
+
|
|
99
|
+
const content = $('.ons-hero--topic').text().trim();
|
|
100
|
+
expect(content).toBe('Topic Text');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('outputs the official statistics badge when officialStatisticsBadge is set to true', () => {
|
|
104
|
+
const $ = cheerio.load(renderComponent('hero', { ...EXAMPLE_HERO, officialStatisticsBadge: true }));
|
|
105
|
+
|
|
106
|
+
expect($('.ons-hero__badge').length).toBe(1);
|
|
107
|
+
expect($('.ons-hero__badge svg title').text().trim()).toBe('Offical Statistics Badge');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('renders curved gradient when variant is `grey`', () => {
|
|
111
|
+
const $ = cheerio.load(renderComponent('hero', { ...EXAMPLE_HERO, variants: 'grey' }));
|
|
112
|
+
expect($('.ons-hero--grey').length).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('outputs the correct breadcrumbs', () => {
|
|
116
|
+
const $ = cheerio.load(
|
|
117
|
+
renderComponent('hero', {
|
|
118
|
+
...EXAMPLE_HERO,
|
|
119
|
+
variants: 'grey',
|
|
120
|
+
breadcrumbs: {
|
|
121
|
+
ariaLabel: 'Breadcrumbs',
|
|
122
|
+
itemsList: [
|
|
123
|
+
{
|
|
124
|
+
url: '/breadcrumb-1',
|
|
125
|
+
text: 'Breadcrumbs 1',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
url: '/breadcrumb-2',
|
|
129
|
+
text: 'Breadcrumbs 2',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const breadcrumbs = $('.ons-breadcrumbs__link');
|
|
137
|
+
expect($(breadcrumbs).length).toBe(2);
|
|
138
|
+
expect($(breadcrumbs[0]).attr('href')).toBe('/breadcrumb-1');
|
|
139
|
+
expect($(breadcrumbs[0]).text().trim()).toBe('Breadcrumbs 1');
|
|
140
|
+
expect($(breadcrumbs[1]).attr('href')).toBe('/breadcrumb-2');
|
|
141
|
+
expect($(breadcrumbs[1]).text().trim()).toBe('Breadcrumbs 2');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('outputs the correct description list value', () => {
|
|
145
|
+
const $ = cheerio.load(
|
|
146
|
+
renderComponent('hero', {
|
|
147
|
+
...EXAMPLE_HERO,
|
|
148
|
+
variants: 'grey',
|
|
149
|
+
descriptionList: {
|
|
150
|
+
termCol: '4',
|
|
151
|
+
descriptionCol: '8',
|
|
152
|
+
itemsList: [
|
|
153
|
+
{
|
|
154
|
+
term: 'term1:',
|
|
155
|
+
descriptions: [
|
|
156
|
+
{
|
|
157
|
+
description: 'description1',
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
term: 'term2:',
|
|
163
|
+
descriptions: [
|
|
164
|
+
{
|
|
165
|
+
description: 'description2',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const descriptionText = $('.ons-description-list__value');
|
|
175
|
+
expect($(descriptionText[0]).text().trim()).toBe('description1');
|
|
176
|
+
expect($(descriptionText[1]).text().trim()).toBe('description2');
|
|
177
|
+
});
|
|
95
178
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
'fullWidth': true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
{% from "components/hero/_macro.njk" import onsHero %}
|
|
6
|
+
{{
|
|
7
|
+
onsHero({
|
|
8
|
+
"variants": 'grey',
|
|
9
|
+
"detailsColumns": '12',
|
|
10
|
+
"officialStatisticsBadge": true,
|
|
11
|
+
"topic": 'Statistical article',
|
|
12
|
+
"title": 'Retail sales rise amid summer discounts and sporting events, according to a first estimate',
|
|
13
|
+
"text": 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum id mattis ligula, nec sollicitudin arcu. Donec non tristique tellus. Donec eget malesuada lorem.',
|
|
14
|
+
"breadcrumbs": {
|
|
15
|
+
"ariaLabel": 'Breadcrumbs',
|
|
16
|
+
"itemsList": [
|
|
17
|
+
{
|
|
18
|
+
"url": '/',
|
|
19
|
+
"text": 'Home'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"url": '/',
|
|
23
|
+
"text": 'Business, industry and trade'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"url": '/',
|
|
27
|
+
"text": 'Retail industry'
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"descriptionList": {
|
|
32
|
+
"termCol": "5",
|
|
33
|
+
"descriptionCol": "7",
|
|
34
|
+
"itemsList": [
|
|
35
|
+
{
|
|
36
|
+
"term": "Release date:",
|
|
37
|
+
"descriptions": [
|
|
38
|
+
{
|
|
39
|
+
"description": "16 August 2024"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"term": "Edition:",
|
|
45
|
+
"descriptions": [
|
|
46
|
+
{
|
|
47
|
+
"description": "Latest"
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"term": "Contact:",
|
|
53
|
+
"descriptions": [
|
|
54
|
+
{
|
|
55
|
+
"description": "Retail Sales team"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"term": "Next release:",
|
|
61
|
+
"descriptions": [
|
|
62
|
+
{
|
|
63
|
+
"description": "20 September 2024"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"term": "Releases:",
|
|
69
|
+
"descriptions": [
|
|
70
|
+
{
|
|
71
|
+
"description": "View previous releases"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}}
|