@oslokommune/punkt-react 6.0.6 → 7.1.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/CHANGELOG.md +36 -0
- package/dist/backlink/BackLink.d.ts +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/inputwrapper/InputWrapper.d.ts +19 -0
- package/dist/punkt-react.es.js +811 -682
- package/dist/punkt-react.umd.js +11 -11
- package/dist/textarea/Textarea.d.ts +20 -3
- package/dist/textinput/Textinput.d.ts +16 -16
- package/package.json +4 -4
- package/src/components/backlink/BackLink.test.tsx +50 -0
- package/src/components/backlink/BackLink.tsx +42 -0
- package/src/components/index.ts +2 -0
- package/src/components/inputwrapper/InputWrapper.tsx +160 -0
- package/src/components/textarea/Textarea.test.tsx +62 -0
- package/src/components/textarea/Textarea.tsx +91 -10
- package/src/components/textinput/Textinput.test.tsx +5 -3
- package/src/components/textinput/Textinput.tsx +105 -172
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/extend-expect'
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render } from '@testing-library/react'
|
|
4
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
|
|
7
|
+
import { PktTextarea } from './Textarea'
|
|
8
|
+
|
|
9
|
+
expect.extend(toHaveNoViolations)
|
|
10
|
+
|
|
11
|
+
describe('PktTextarea', () => {
|
|
12
|
+
test('renders input field with correct props', () => {
|
|
13
|
+
const { getByLabelText } = render(<PktTextarea label="Input Label" id="inputId" />)
|
|
14
|
+
|
|
15
|
+
const inputElement = getByLabelText('Input Label')
|
|
16
|
+
expect(inputElement).toBeInTheDocument()
|
|
17
|
+
expect(inputElement.tagName).toBe('TEXTAREA')
|
|
18
|
+
expect(inputElement.id).toBe('inputId')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('renders error message when hasError prop is true', () => {
|
|
22
|
+
const { getByText } = render(
|
|
23
|
+
<PktTextarea label="Input Label" id="inputId" hasError errorMessage="Input error" />,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const errorMessage = getByText('Input error')
|
|
27
|
+
expect(errorMessage).toBeInTheDocument()
|
|
28
|
+
expect(errorMessage).toHaveClass('pkt-alert__text')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('PktTextarea', () => {
|
|
32
|
+
test('toggles helptext class', () => {
|
|
33
|
+
const { getByText, container } = render(
|
|
34
|
+
<PktTextarea
|
|
35
|
+
label="Input Label"
|
|
36
|
+
id="inputId"
|
|
37
|
+
helptext="Help Text"
|
|
38
|
+
helptextDropdown="Help Text"
|
|
39
|
+
/>,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const expandButton = getByText('Les mer')
|
|
43
|
+
const helptextElement = container.querySelector(
|
|
44
|
+
'.pkt-inputwrapper__helptext-expandable-closed',
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
fireEvent.click(expandButton)
|
|
48
|
+
expect(helptextElement).toHaveClass('pkt-inputwrapper__helptext-expandable-open')
|
|
49
|
+
|
|
50
|
+
fireEvent.click(expandButton)
|
|
51
|
+
expect(helptextElement).toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('accessibility', () => {
|
|
56
|
+
it('renders with no wcag errors with axe', async () => {
|
|
57
|
+
const { container } = render(<PktTextarea label="Input Label" id="inputId" />)
|
|
58
|
+
const results = await axe(container)
|
|
59
|
+
expect(results).toHaveNoViolations()
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -1,21 +1,102 @@
|
|
|
1
1
|
import React, { ForwardedRef, forwardRef, TextareaHTMLAttributes } from 'react'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { PktInputWrapper } from '../inputwrapper/InputWrapper'
|
|
4
|
+
|
|
5
|
+
export interface IPktTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
4
6
|
id: string
|
|
7
|
+
ariaDescribedby?: string
|
|
8
|
+
ariaLabelledby?: string
|
|
9
|
+
counter?: boolean
|
|
10
|
+
counterMaxLength?: number
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
errorMessage?: string
|
|
13
|
+
hasError?: boolean
|
|
14
|
+
helptext?: string
|
|
15
|
+
helptextDropdown?: string
|
|
16
|
+
helptextDropdownButton?: string
|
|
17
|
+
inline?: boolean
|
|
5
18
|
label: string
|
|
19
|
+
name?: string
|
|
20
|
+
optional?: boolean
|
|
21
|
+
placeholder?: string
|
|
22
|
+
required?: boolean
|
|
23
|
+
rows?: number
|
|
24
|
+
useWrapper?: boolean
|
|
25
|
+
value?: string
|
|
6
26
|
}
|
|
7
27
|
|
|
8
28
|
export const PktTextarea = forwardRef(
|
|
9
|
-
(
|
|
29
|
+
(
|
|
30
|
+
{
|
|
31
|
+
id,
|
|
32
|
+
ariaDescribedby,
|
|
33
|
+
ariaLabelledby,
|
|
34
|
+
counter,
|
|
35
|
+
counterMaxLength,
|
|
36
|
+
className,
|
|
37
|
+
disabled,
|
|
38
|
+
errorMessage,
|
|
39
|
+
hasError,
|
|
40
|
+
helptext,
|
|
41
|
+
helptextDropdown,
|
|
42
|
+
helptextDropdownButton,
|
|
43
|
+
inline,
|
|
44
|
+
label,
|
|
45
|
+
name,
|
|
46
|
+
optional,
|
|
47
|
+
placeholder,
|
|
48
|
+
required,
|
|
49
|
+
rows,
|
|
50
|
+
useWrapper,
|
|
51
|
+
value = '',
|
|
52
|
+
...props
|
|
53
|
+
}: IPktTextareaProps,
|
|
54
|
+
ref: ForwardedRef<HTMLTextAreaElement>,
|
|
55
|
+
) => {
|
|
56
|
+
const classNames = [className, 'pkt-textinput', 'pkt-textarea'].join(' ')
|
|
57
|
+
const labelledBy = ariaLabelledby || `${id}-label`
|
|
10
58
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
59
|
+
<PktInputWrapper
|
|
60
|
+
ariaDescribedby={ariaDescribedby}
|
|
61
|
+
className={classNames}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
errorMessage={errorMessage}
|
|
64
|
+
forId={id}
|
|
65
|
+
hasError={hasError}
|
|
66
|
+
helptext={helptext}
|
|
67
|
+
helptextDropdown={helptextDropdown}
|
|
68
|
+
helptextDropdownButton={helptextDropdownButton}
|
|
69
|
+
inline={inline}
|
|
70
|
+
label={label}
|
|
71
|
+
optional={optional}
|
|
72
|
+
required={required}
|
|
73
|
+
useWrapper={useWrapper}
|
|
74
|
+
>
|
|
75
|
+
<textarea
|
|
76
|
+
ref={ref}
|
|
77
|
+
className={`pkt-textinput__input ${
|
|
78
|
+
counterMaxLength && value?.length > counterMaxLength
|
|
79
|
+
? 'pkt-textinput__input--counter-error'
|
|
80
|
+
: ''
|
|
81
|
+
}`}
|
|
82
|
+
name={name || id}
|
|
83
|
+
id={id}
|
|
84
|
+
placeholder={placeholder}
|
|
85
|
+
value={value}
|
|
86
|
+
disabled={disabled}
|
|
87
|
+
rows={rows}
|
|
88
|
+
aria-labelledby={labelledBy}
|
|
89
|
+
aria-invalid={hasError}
|
|
90
|
+
aria-required={required}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
{counter && (
|
|
94
|
+
<div className="pkt-textinput__counter" aria-live="polite" aria-atomic={true}>
|
|
95
|
+
{value?.length || 0}
|
|
96
|
+
{counterMaxLength && `/${counterMaxLength}`}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</PktInputWrapper>
|
|
17
100
|
)
|
|
18
101
|
},
|
|
19
102
|
)
|
|
20
|
-
|
|
21
|
-
PktTextarea.displayName = 'PktTextarea'
|
|
@@ -40,13 +40,15 @@ describe('PktTextinput', () => {
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
const expandButton = getByText('Les mer')
|
|
43
|
-
const helptextElement = container.querySelector(
|
|
43
|
+
const helptextElement = container.querySelector(
|
|
44
|
+
'.pkt-inputwrapper__helptext-expandable-closed',
|
|
45
|
+
)
|
|
44
46
|
|
|
45
47
|
fireEvent.click(expandButton)
|
|
46
|
-
expect(helptextElement).toHaveClass('pkt-
|
|
48
|
+
expect(helptextElement).toHaveClass('pkt-inputwrapper__helptext-expandable-open')
|
|
47
49
|
|
|
48
50
|
fireEvent.click(expandButton)
|
|
49
|
-
expect(helptextElement).toHaveClass('pkt-
|
|
51
|
+
expect(helptextElement).toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
|
|
50
52
|
})
|
|
51
53
|
})
|
|
52
54
|
|
|
@@ -1,197 +1,105 @@
|
|
|
1
|
-
import React, { ForwardedRef, forwardRef,
|
|
1
|
+
import React, { ForwardedRef, forwardRef, InputHTMLAttributes } from 'react'
|
|
2
2
|
|
|
3
|
-
import { PktAlert } from '../alert/Alert'
|
|
4
|
-
import { PktButton } from '../button/Button'
|
|
5
3
|
import { PktIcon } from '../icon/Icon'
|
|
4
|
+
import { PktInputWrapper } from '../inputwrapper/InputWrapper'
|
|
6
5
|
|
|
7
|
-
export interface IPktTextinput extends
|
|
6
|
+
export interface IPktTextinput extends InputHTMLAttributes<HTMLInputElement> {
|
|
8
7
|
id: string
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
ariaDescribedby?: string
|
|
9
|
+
ariaLabelledby?: string
|
|
10
|
+
autocomplete?: string
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
errorMessage?: string
|
|
13
|
+
hasError?: boolean
|
|
12
14
|
helptext?: string
|
|
13
15
|
helptextDropdown?: string
|
|
14
16
|
helptextDropdownButton?: string
|
|
17
|
+
iconNameRight?: string
|
|
18
|
+
inline?: boolean
|
|
19
|
+
label: string
|
|
20
|
+
name?: string
|
|
15
21
|
optional?: boolean
|
|
16
|
-
required?: boolean
|
|
17
|
-
hasError?: boolean
|
|
18
|
-
errorMessage?: string
|
|
19
22
|
placeholder?: string
|
|
23
|
+
prefix?: string
|
|
24
|
+
required?: boolean
|
|
25
|
+
suffix?: string
|
|
20
26
|
type?: string
|
|
21
|
-
|
|
27
|
+
useWrapper?: boolean
|
|
22
28
|
value?: string
|
|
23
|
-
suffix?: string
|
|
24
|
-
prefix?: string
|
|
25
|
-
iconNameRight?: string
|
|
26
|
-
disabled?: boolean
|
|
27
|
-
inline?: boolean
|
|
28
|
-
ariaLabelledby?: string
|
|
29
|
-
ariaDescribedby?: string
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export const PktTextinput = forwardRef(
|
|
33
32
|
(
|
|
34
33
|
{
|
|
35
34
|
id,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
ariaDescribedby,
|
|
36
|
+
ariaLabelledby,
|
|
37
|
+
autocomplete = 'off',
|
|
38
|
+
className,
|
|
39
|
+
disabled = false,
|
|
40
|
+
errorMessage,
|
|
41
|
+
hasError = false,
|
|
39
42
|
helptext,
|
|
40
43
|
helptextDropdown,
|
|
41
44
|
helptextDropdownButton,
|
|
45
|
+
iconNameRight,
|
|
46
|
+
inline = false,
|
|
47
|
+
label,
|
|
48
|
+
name,
|
|
42
49
|
optional = false,
|
|
43
|
-
required = false,
|
|
44
|
-
hasError = false,
|
|
45
|
-
errorMessage,
|
|
46
50
|
placeholder,
|
|
51
|
+
prefix,
|
|
52
|
+
required = false,
|
|
53
|
+
suffix,
|
|
47
54
|
type = 'text',
|
|
48
|
-
|
|
55
|
+
useWrapper = true,
|
|
49
56
|
value,
|
|
50
|
-
suffix,
|
|
51
|
-
prefix,
|
|
52
|
-
iconNameRight,
|
|
53
|
-
disabled = false,
|
|
54
|
-
inline = false,
|
|
55
|
-
ariaLabelledby,
|
|
56
|
-
ariaDescribedby,
|
|
57
|
-
className,
|
|
58
57
|
...props
|
|
59
58
|
}: IPktTextinput,
|
|
60
59
|
ref: ForwardedRef<HTMLInputElement>,
|
|
61
60
|
) => {
|
|
62
|
-
const [
|
|
63
|
-
|
|
64
|
-
const toggleHelpText = () => {
|
|
65
|
-
setIsHelpTextOpen(!isHelpTextOpen)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const tagClass = () => {
|
|
69
|
-
if (optional) {
|
|
70
|
-
return 'pkt-tag pkt-tag--small pkt-tag--thin-text pkt-tag--blue-light'
|
|
71
|
-
} else if (required) {
|
|
72
|
-
return 'pkt-tag pkt-tag--small pkt-tag--thin-text pkt-tag--beige'
|
|
73
|
-
} else {
|
|
74
|
-
return ''
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const tagText = optional ? 'Valgfritt' : required ? 'Må fylles ut' : ''
|
|
79
|
-
|
|
80
|
-
const disabledClass = disabled ? 'pkt-textinput--disabled' : ''
|
|
81
|
-
const inlineClass = inline ? 'pkt-textinput--inline' : ''
|
|
82
|
-
const errorClass = hasError ? 'pkt-textinput--error' : ''
|
|
83
|
-
const classNames = [className, 'pkt-textinput', disabledClass, inlineClass, errorClass].join(
|
|
84
|
-
' ',
|
|
85
|
-
)
|
|
86
|
-
const hasDropDown = !!helptextDropdown && helptextDropdown !== ''
|
|
87
|
-
const WrapperElement = hasDropDown ? 'div' : 'label'
|
|
88
|
-
const LabelElement = hasDropDown ? 'h2' : 'span'
|
|
61
|
+
const classNames = [className, 'pkt-textinput'].join(' ')
|
|
62
|
+
const labelledBy = ariaLabelledby || `${id}-label`
|
|
89
63
|
return (
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
>
|
|
127
|
-
<PktAlert>{helptextDropdown}</PktAlert>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
<label
|
|
131
|
-
htmlFor={id}
|
|
132
|
-
className="pkt-textinput__label pkt-sr-only"
|
|
133
|
-
aria-describedby={ariaDescribedby}
|
|
134
|
-
>
|
|
135
|
-
{label}
|
|
136
|
-
</label>
|
|
137
|
-
</>
|
|
138
|
-
)}
|
|
139
|
-
|
|
140
|
-
{prefix && (
|
|
141
|
-
<div className="pkt-textinput__input-prefix-wrapper">
|
|
142
|
-
<div className="pkt-textinput__input-prefix">{prefix}</div>
|
|
143
|
-
<input
|
|
144
|
-
ref={ref}
|
|
145
|
-
className="pkt-textinput__input"
|
|
146
|
-
type={type}
|
|
147
|
-
name={name || id}
|
|
148
|
-
id={id}
|
|
149
|
-
placeholder={placeholder}
|
|
150
|
-
autoComplete={autocomplete}
|
|
151
|
-
value={value}
|
|
152
|
-
disabled={disabled}
|
|
153
|
-
aria-required={required}
|
|
154
|
-
aria-invalid={hasError}
|
|
155
|
-
aria-labelledby={ariaLabelledby}
|
|
156
|
-
{...props}
|
|
157
|
-
/>
|
|
158
|
-
</div>
|
|
159
|
-
)}
|
|
160
|
-
|
|
161
|
-
{!prefix && (suffix || iconNameRight) && (
|
|
162
|
-
<div className="pkt-textinput__input-suffix-wrapper">
|
|
163
|
-
<input
|
|
164
|
-
ref={ref}
|
|
165
|
-
className="pkt-textinput__input"
|
|
166
|
-
type={type}
|
|
167
|
-
name={name || id}
|
|
168
|
-
id={id}
|
|
169
|
-
placeholder={placeholder}
|
|
170
|
-
autoComplete={autocomplete}
|
|
171
|
-
value={value}
|
|
172
|
-
disabled={disabled}
|
|
173
|
-
aria-required={required}
|
|
174
|
-
aria-invalid={hasError}
|
|
175
|
-
aria-labelledby={ariaLabelledby}
|
|
176
|
-
{...props}
|
|
177
|
-
/>
|
|
178
|
-
|
|
179
|
-
{suffix && (
|
|
180
|
-
<p className="pkt-textinput__input-suffix">
|
|
181
|
-
{suffix}
|
|
182
|
-
{iconNameRight && (
|
|
183
|
-
<PktIcon className="pkt-textinput__input-suffix-icon" name={iconNameRight} />
|
|
184
|
-
)}
|
|
185
|
-
</p>
|
|
186
|
-
)}
|
|
187
|
-
|
|
188
|
-
{!suffix && iconNameRight && (
|
|
189
|
-
<PktIcon className="pkt-textinput__input-icon" name={iconNameRight} />
|
|
190
|
-
)}
|
|
191
|
-
</div>
|
|
192
|
-
)}
|
|
64
|
+
<PktInputWrapper
|
|
65
|
+
ariaDescribedby={ariaDescribedby}
|
|
66
|
+
className={classNames}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
errorMessage={errorMessage}
|
|
69
|
+
forId={id}
|
|
70
|
+
hasError={hasError}
|
|
71
|
+
helptext={helptext}
|
|
72
|
+
helptextDropdown={helptextDropdown}
|
|
73
|
+
helptextDropdownButton={helptextDropdownButton}
|
|
74
|
+
inline={inline}
|
|
75
|
+
label={label}
|
|
76
|
+
optional={optional}
|
|
77
|
+
required={required}
|
|
78
|
+
useWrapper={useWrapper}
|
|
79
|
+
>
|
|
80
|
+
{prefix && (
|
|
81
|
+
<div className="pkt-textinput__input-prefix-wrapper">
|
|
82
|
+
<div className="pkt-textinput__input-prefix">{prefix}</div>
|
|
83
|
+
<input
|
|
84
|
+
ref={ref}
|
|
85
|
+
className="pkt-textinput__input"
|
|
86
|
+
type={type}
|
|
87
|
+
name={name || id}
|
|
88
|
+
id={id}
|
|
89
|
+
placeholder={placeholder}
|
|
90
|
+
autoComplete={autocomplete}
|
|
91
|
+
value={value}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
aria-required={required}
|
|
94
|
+
aria-invalid={hasError}
|
|
95
|
+
aria-labelledby={labelledBy}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
193
100
|
|
|
194
|
-
|
|
101
|
+
{!prefix && (suffix || iconNameRight) && (
|
|
102
|
+
<div className="pkt-textinput__input-suffix-wrapper">
|
|
195
103
|
<input
|
|
196
104
|
ref={ref}
|
|
197
105
|
className="pkt-textinput__input"
|
|
@@ -204,18 +112,43 @@ export const PktTextinput = forwardRef(
|
|
|
204
112
|
disabled={disabled}
|
|
205
113
|
aria-required={required}
|
|
206
114
|
aria-invalid={hasError}
|
|
207
|
-
aria-labelledby={
|
|
115
|
+
aria-labelledby={labelledBy}
|
|
208
116
|
{...props}
|
|
209
117
|
/>
|
|
210
|
-
)}
|
|
211
|
-
</WrapperElement>
|
|
212
118
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
119
|
+
{suffix && (
|
|
120
|
+
<p className="pkt-textinput__input-suffix">
|
|
121
|
+
{suffix}
|
|
122
|
+
{iconNameRight && (
|
|
123
|
+
<PktIcon className="pkt-textinput__input-suffix-icon" name={iconNameRight} />
|
|
124
|
+
)}
|
|
125
|
+
</p>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{!suffix && iconNameRight && (
|
|
129
|
+
<PktIcon className="pkt-textinput__input-icon" name={iconNameRight} />
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{!prefix && !suffix && !iconNameRight && (
|
|
135
|
+
<input
|
|
136
|
+
ref={ref}
|
|
137
|
+
className="pkt-textinput__input"
|
|
138
|
+
type={type}
|
|
139
|
+
name={name || id}
|
|
140
|
+
id={id}
|
|
141
|
+
placeholder={placeholder}
|
|
142
|
+
autoComplete={autocomplete}
|
|
143
|
+
value={value}
|
|
144
|
+
disabled={disabled}
|
|
145
|
+
aria-required={required}
|
|
146
|
+
aria-invalid={hasError}
|
|
147
|
+
aria-labelledby={labelledBy}
|
|
148
|
+
{...props}
|
|
149
|
+
/>
|
|
217
150
|
)}
|
|
218
|
-
</
|
|
151
|
+
</PktInputWrapper>
|
|
219
152
|
)
|
|
220
153
|
},
|
|
221
154
|
)
|