@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.
@@ -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
- interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
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
- ({ id, label, ...props }: TextareaProps, ref: ForwardedRef<HTMLTextAreaElement>): React.ReactElement => {
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
- <div className="pkt-form-group">
12
- <label htmlFor={id} className="pkt-form-label">
13
- {label}
14
- </label>
15
- <textarea className="pkt-form-textarea" id={id} ref={ref} {...props}></textarea>
16
- </div>
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('.pkt-textinput__helptext-expandable-closed')
43
+ const helptextElement = container.querySelector(
44
+ '.pkt-inputwrapper__helptext-expandable-closed',
45
+ )
44
46
 
45
47
  fireEvent.click(expandButton)
46
- expect(helptextElement).toHaveClass('pkt-textinput__helptext-expandable-open')
48
+ expect(helptextElement).toHaveClass('pkt-inputwrapper__helptext-expandable-open')
47
49
 
48
50
  fireEvent.click(expandButton)
49
- expect(helptextElement).toHaveClass('pkt-textinput__helptext-expandable-closed')
51
+ expect(helptextElement).toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
50
52
  })
51
53
  })
52
54
 
@@ -1,197 +1,105 @@
1
- import React, { ForwardedRef, forwardRef, useState } from 'react'
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 React.InputHTMLAttributes<HTMLInputElement> {
6
+ export interface IPktTextinput extends InputHTMLAttributes<HTMLInputElement> {
8
7
  id: string
9
- label: string
10
- name?: string
11
- srOnlyLabel?: boolean
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
- autocomplete?: string
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
- label,
37
- name,
38
- srOnlyLabel = false,
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
- autocomplete = 'off',
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 [isHelpTextOpen, setIsHelpTextOpen] = useState(false)
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
- <div className={classNames}>
91
- <WrapperElement
92
- htmlFor={id}
93
- className={`pkt-textinput__label ${srOnlyLabel ? 'pkt-sr-only' : ''}`}
94
- aria-describedby={hasDropDown ? '' : ariaDescribedby}
95
- >
96
- <LabelElement>
97
- {label}
98
- {tagText !== '' && <span className={tagClass()}>{tagText}</span>}
99
- </LabelElement>
100
- {helptext && <div className="pkt-textinput__helptext">{helptext}</div>}
101
-
102
- {hasDropDown && (
103
- <>
104
- <PktButton
105
- skin="tertiary"
106
- size="small"
107
- variant="icon-right"
108
- iconName={isHelpTextOpen ? 'chevron-thin-up' : 'chevron-thin-down'}
109
- className="pkt-textinput__helptext-expandable pkt-link pkt-link--icon-right"
110
- onClick={toggleHelpText}
111
- >
112
- {helptextDropdownButton ? (
113
- helptextDropdownButton
114
- ) : (
115
- <>
116
- Les mer <span className="pkt-sr-only">om inputfeltet</span>
117
- </>
118
- )}
119
- </PktButton>
120
- <div
121
- className={`pkt-textinput__helptext ${
122
- isHelpTextOpen
123
- ? 'pkt-textinput__helptext-expandable-open'
124
- : 'pkt-textinput__helptext-expandable-closed'
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
- {!prefix && !suffix && !iconNameRight && !helptextDropdown && (
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={ariaLabelledby}
115
+ aria-labelledby={labelledBy}
208
116
  {...props}
209
117
  />
210
- )}
211
- </WrapperElement>
212
118
 
213
- {hasError && (
214
- <PktAlert skin="error" aria-live="assertive">
215
- {errorMessage}
216
- </PktAlert>
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
- </div>
151
+ </PktInputWrapper>
219
152
  )
220
153
  },
221
154
  )