@oslokommune/punkt-react 17.0.10 → 17.1.1
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 +18 -0
- package/dist/components/combobox/types.d.ts +4 -0
- package/dist/components/datepicker/types.d.ts +2 -0
- package/dist/components/select/Select.d.ts +2 -0
- package/dist/components/textarea/Textarea.d.ts +1 -0
- package/dist/components/textinput/Textinput.d.ts +1 -0
- package/dist/components/timepicker/types.d.ts +2 -0
- package/dist/punkt-react.cjs +1 -1
- package/dist/punkt-react.js +1765 -1715
- package/dist/shared-types/booleanish.d.ts +11 -0
- package/dist/shared-types/index.d.ts +1 -1
- package/dist/shared-utils/forms/described-by.d.ts +24 -0
- package/package.json +3 -3
- package/src/components/combobox/Combobox.tsx +2 -1
- package/src/components/combobox/types.ts +4 -0
- package/src/components/combobox/useComboboxState.ts +5 -1
- package/src/components/datepicker/Datepicker.tsx +7 -1
- package/src/components/datepicker/types.ts +2 -0
- package/src/components/datepicker/useDatepickerState.ts +3 -1
- package/src/components/fileupload/FileUpload.tsx +20 -18
- package/src/components/helptext/Helptext.tsx +3 -0
- package/src/components/inputwrapper/InputWrapper.tsx +20 -8
- package/src/components/select/Select.tsx +46 -32
- package/src/components/textarea/Textarea.tsx +59 -47
- package/src/components/textinput/Textinput.tsx +79 -67
- package/src/components/timepicker/Timepicker.tsx +3 -0
- package/src/components/timepicker/types.ts +2 -0
|
@@ -11,3 +11,14 @@ export declare const booleanishConverter: {
|
|
|
11
11
|
fromAttribute(value: string | boolean | null): boolean;
|
|
12
12
|
toAttribute(value: boolean): string | null;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Som booleanishConverter, men skiller «ikke satt» fra `false`.
|
|
16
|
+
*
|
|
17
|
+
* Brukes for props der komponenten skal utlede en fornuftig standard når
|
|
18
|
+
* forbrukeren ikke har sagt noe — for eksempel `counter`, som slås på av seg
|
|
19
|
+
* selv når det finnes noe å telle, men skal kunne skrus av eksplisitt.
|
|
20
|
+
*/
|
|
21
|
+
export declare const nullableBooleanishConverter: {
|
|
22
|
+
fromAttribute(value: string | boolean | null): boolean | null;
|
|
23
|
+
toAttribute(value: boolean | null): string | null;
|
|
24
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { Booleanish } from './booleanish';
|
|
2
|
-
export { booleanishConverter } from './booleanish';
|
|
2
|
+
export { booleanishConverter, nullableBooleanishConverter } from './booleanish';
|
|
3
3
|
export type { TAriaBoolean, TAriaBooleanMixed, TAriaCurrent, TAriaHasPopup, TAriaInvalid, TAriaLive, TAriaRelevant, TAriaSort, TAriaAutoComplete, TAriaOrientation, IAriaAttributes, TAlertRole, } from './aria';
|
|
4
4
|
export type { THeadingLevel, THeadingSize, THeadingWeight } from './heading';
|
|
5
5
|
export type { User, Representing, UserMenuItem, InternalMenuItemBase, InternalMenuLink, InternalMenuButton, TInternalMenuItem, TSlotMenuVariant, THeaderMenu, TLogOutButtonPlacement, THeaderPosition, THeaderScrollBehavior, } from './header';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bygger `aria-describedby` for et skjemafelt.
|
|
3
|
+
*
|
|
4
|
+
* Beskrivelsen må ligge på selve kontrollen — `aria-describedby` på en `<label>`
|
|
5
|
+
* bidrar ikke til feltets tilgjengelige beskrivelse, og hjelpeteksten blir da
|
|
6
|
+
* aldri lest opp når fokus havner i feltet.
|
|
7
|
+
*
|
|
8
|
+
* Feilmeldingen er med vilje ikke med: den kobles gjennom `aria-errormessage`,
|
|
9
|
+
* som er den riktige kanalen, og ville ellers blitt lest opp to ganger.
|
|
10
|
+
*/
|
|
11
|
+
export declare const describedByIds: (options: {
|
|
12
|
+
/** Id-en feltet bruker (samme som wrapperens forId). */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Om det finnes en hjelpetekst å beskrive feltet med. */
|
|
15
|
+
hasHelptext?: boolean;
|
|
16
|
+
/** Om telleren er aktiv. Statusregionen leses bare opp når grensa er brutt. */
|
|
17
|
+
hasCounter?: boolean;
|
|
18
|
+
/** Forbrukerens egen `ariaDescribedby`, hvis satt. */
|
|
19
|
+
ariaDescribedby?: string | null;
|
|
20
|
+
}) => string | undefined;
|
|
21
|
+
/** Id-en til den skjulte statusregionen som melder at tellergrensa er brutt. */
|
|
22
|
+
export declare const counterStatusId: (id: string) => string;
|
|
23
|
+
/** Om tellergrensa er brutt. */
|
|
24
|
+
export declare const isCounterExceeded: (current: number | undefined, max: number | undefined) => boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-react",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.1.1",
|
|
4
4
|
"description": "React komponentbibliotek til Punkt, et designsystem laget av Oslo Origo",
|
|
5
5
|
"homepage": "https://punkt.oslo.kommune.no",
|
|
6
6
|
"author": "Team Designsystem, Oslo Origo",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@eslint/eslintrc": "^3.3.5",
|
|
46
46
|
"@eslint/js": "^10.0.1",
|
|
47
47
|
"@oslokommune/punkt-assets": "^17.0.3",
|
|
48
|
-
"@oslokommune/punkt-css": "^17.
|
|
48
|
+
"@oslokommune/punkt-css": "^17.1.1",
|
|
49
49
|
"@testing-library/jest-dom": "^6.9.1",
|
|
50
50
|
"@testing-library/react": "^16.3.2",
|
|
51
51
|
"@testing-library/user-event": "^14.6.1",
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
102
102
|
},
|
|
103
103
|
"license": "MIT",
|
|
104
|
-
"gitHead": "
|
|
104
|
+
"gitHead": "0e600db899cfda4ac87979c94724908424ba56c8"
|
|
105
105
|
}
|
|
@@ -28,7 +28,7 @@ export const PktCombobox = forwardRef<HTMLDivElement, IPktCombobox>((props, ref)
|
|
|
28
28
|
<div className={outerClasses} ref={state.wrapperRef} id={state.id || undefined}>
|
|
29
29
|
<PktInputWrapper
|
|
30
30
|
forId={wrapperForId}
|
|
31
|
-
hasFieldset={!hasTextInput}
|
|
31
|
+
hasFieldset={props.hasFieldset ?? !hasTextInput}
|
|
32
32
|
label={state.label || ''}
|
|
33
33
|
helptext={state.helptext}
|
|
34
34
|
helptextDropdown={state.helptextDropdown}
|
|
@@ -42,6 +42,7 @@ export const PktCombobox = forwardRef<HTMLDivElement, IPktCombobox>((props, ref)
|
|
|
42
42
|
requiredText={state.requiredText}
|
|
43
43
|
tagText={state.tagText}
|
|
44
44
|
useWrapper={state.useWrapper}
|
|
45
|
+
inline={state.inline}
|
|
45
46
|
size={props.inputSize ?? 'medium'}
|
|
46
47
|
counter={state.hasCounter}
|
|
47
48
|
counterCurrent={state.values.length}
|
|
@@ -50,6 +50,9 @@ export interface IPktCombobox {
|
|
|
50
50
|
optionalText?: string
|
|
51
51
|
tagText?: string
|
|
52
52
|
useWrapper?: boolean
|
|
53
|
+
inline?: boolean
|
|
54
|
+
counter?: boolean
|
|
55
|
+
hasFieldset?: boolean
|
|
53
56
|
inputSize?: 'small' | 'medium' | 'xsmall'
|
|
54
57
|
|
|
55
58
|
// Events
|
|
@@ -110,6 +113,7 @@ export interface IComboboxState {
|
|
|
110
113
|
requiredText?: string
|
|
111
114
|
tagText?: string
|
|
112
115
|
useWrapper: boolean
|
|
116
|
+
inline: boolean
|
|
113
117
|
inputSize: 'small' | 'medium' | 'xsmall'
|
|
114
118
|
name?: string
|
|
115
119
|
className?: string
|
|
@@ -72,6 +72,8 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
72
72
|
requiredText,
|
|
73
73
|
tagText,
|
|
74
74
|
useWrapper = true,
|
|
75
|
+
inline = false,
|
|
76
|
+
counter,
|
|
75
77
|
inputSize = 'medium',
|
|
76
78
|
name,
|
|
77
79
|
className,
|
|
@@ -184,7 +186,8 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
184
186
|
|
|
185
187
|
// Derived state
|
|
186
188
|
const maxIsReached = isMaxSelectionReached(values.length, maxlength ?? null)
|
|
187
|
-
|
|
189
|
+
// Telleren vises av seg selv når det er noe å telle, men kan slås av eksplisitt
|
|
190
|
+
const hasCounter = counter ?? (multiple && maxlength != null)
|
|
188
191
|
|
|
189
192
|
// Filter options by search (typeahead uses fulltext filtering, includeSearch uses same)
|
|
190
193
|
const filteredOptions = useMemo(() => {
|
|
@@ -1105,6 +1108,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
1105
1108
|
requiredText,
|
|
1106
1109
|
tagText,
|
|
1107
1110
|
useWrapper,
|
|
1111
|
+
inline,
|
|
1108
1112
|
inputSize,
|
|
1109
1113
|
name,
|
|
1110
1114
|
className,
|
|
@@ -12,7 +12,12 @@ export type { IPktDatepicker } from './types'
|
|
|
12
12
|
export const PktDatepicker = forwardRef<HTMLDivElement, IPktDatepicker>((props, ref) => {
|
|
13
13
|
const state = useDatepickerState(props, ref)
|
|
14
14
|
|
|
15
|
-
const outerClasses = [
|
|
15
|
+
const outerClasses = [
|
|
16
|
+
'pkt-datepicker',
|
|
17
|
+
`pkt-datepicker--${state.inputSize}`,
|
|
18
|
+
state.fullwidth ? 'pkt-datepicker--fullwidth' : '',
|
|
19
|
+
state.className,
|
|
20
|
+
]
|
|
16
21
|
.filter(Boolean)
|
|
17
22
|
.join(' ')
|
|
18
23
|
|
|
@@ -20,6 +25,7 @@ export const PktDatepicker = forwardRef<HTMLDivElement, IPktDatepicker>((props,
|
|
|
20
25
|
<div className={outerClasses} id={state.id || undefined}>
|
|
21
26
|
<PktInputWrapper
|
|
22
27
|
ref={state.wrapperRef}
|
|
28
|
+
ariaDescribedby={props.ariaDescribedby}
|
|
23
29
|
forId={state.inputId}
|
|
24
30
|
label={state.label}
|
|
25
31
|
hasFieldset={state.hasFieldset}
|
|
@@ -5,6 +5,7 @@ import type { TPktInputSize } from 'shared-types/size'
|
|
|
5
5
|
export interface IPktDatepicker extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
|
|
6
6
|
id: string
|
|
7
7
|
label: string
|
|
8
|
+
ariaDescribedby?: string
|
|
8
9
|
|
|
9
10
|
// Datepicker-specific
|
|
10
11
|
value?: string | string[]
|
|
@@ -27,6 +28,7 @@ export interface IPktDatepicker extends Omit<InputHTMLAttributes<HTMLInputElemen
|
|
|
27
28
|
inputSize?: TPktInputSize
|
|
28
29
|
hasFieldset?: boolean
|
|
29
30
|
inline?: boolean
|
|
31
|
+
counter?: boolean
|
|
30
32
|
helptext?: string | ReactNode
|
|
31
33
|
helptextDropdown?: string | ReactNode
|
|
32
34
|
helptextDropdownButton?: string
|
|
@@ -38,6 +38,7 @@ export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRe
|
|
|
38
38
|
fullwidth = false,
|
|
39
39
|
inputSize = 'medium' as TPktInputSize,
|
|
40
40
|
hasFieldset = false,
|
|
41
|
+
counter,
|
|
41
42
|
inline = false,
|
|
42
43
|
helptext,
|
|
43
44
|
helptextDropdown,
|
|
@@ -152,7 +153,8 @@ export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRe
|
|
|
152
153
|
}, [currentmonth, maxStr])
|
|
153
154
|
|
|
154
155
|
const isInputDisabled = disabled || (multiple && maxlength != null && values.length >= maxlength)
|
|
155
|
-
|
|
156
|
+
// Telleren vises av seg selv når det er noe å telle, men kan slås av eksplisitt
|
|
157
|
+
const hasCounter = counter ?? (multiple && maxlength != null)
|
|
156
158
|
const inputId = `${id}-input`
|
|
157
159
|
const formValue = values.join(',')
|
|
158
160
|
|
|
@@ -421,27 +421,29 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
|
|
|
421
421
|
|
|
422
422
|
if (label) {
|
|
423
423
|
return (
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
424
|
+
<div className="pkt-fileupload-component">
|
|
425
|
+
<PktInputWrapper
|
|
426
|
+
forId={id}
|
|
427
|
+
label={label}
|
|
428
|
+
helptext={helptext}
|
|
429
|
+
helptextDropdown={helptextDropdown}
|
|
430
|
+
helptextDropdownButton={helptextDropdownButton}
|
|
431
|
+
disabled={disabled}
|
|
432
|
+
hasError={hasError}
|
|
433
|
+
optionalTag={optionalTag}
|
|
434
|
+
optionalText={optionalText}
|
|
435
|
+
requiredTag={requiredTag}
|
|
436
|
+
requiredText={requiredText}
|
|
437
|
+
tagText={tagText}
|
|
438
|
+
className={classNames('pkt-fileupload-wrapper', { 'pkt-fileupload-wrapper--full-width': fullwidth })}
|
|
439
|
+
>
|
|
440
|
+
{fileUploadContent}
|
|
441
|
+
</PktInputWrapper>
|
|
442
|
+
</div>
|
|
441
443
|
)
|
|
442
444
|
}
|
|
443
445
|
|
|
444
|
-
return fileUploadContent
|
|
446
|
+
return <div className="pkt-fileupload-component">{fileUploadContent}</div>
|
|
445
447
|
},
|
|
446
448
|
)
|
|
447
449
|
|
|
@@ -63,6 +63,8 @@ export const PktHelptext = forwardRef<HTMLDivElement, IPktHelptextProps>(functio
|
|
|
63
63
|
<button
|
|
64
64
|
type="button"
|
|
65
65
|
className="pkt-link pkt-link--icon-right pkt-btn pkt-btn--small pkt-btn--tertiary pkt-btn--icon-right"
|
|
66
|
+
aria-expanded={isOpen}
|
|
67
|
+
aria-controls={`${forId}-helptext-expandable`}
|
|
66
68
|
onClick={toggle}
|
|
67
69
|
>
|
|
68
70
|
<PktIcon
|
|
@@ -77,6 +79,7 @@ export const PktHelptext = forwardRef<HTMLDivElement, IPktHelptextProps>(functio
|
|
|
77
79
|
/>
|
|
78
80
|
</button>
|
|
79
81
|
<div
|
|
82
|
+
id={`${forId}-helptext-expandable`}
|
|
80
83
|
className={classNames(
|
|
81
84
|
'pkt-inputwrapper__helptext',
|
|
82
85
|
isOpen
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { ForwardedRef, forwardRef, type ReactNode, RefAttributes } from 'react'
|
|
4
4
|
|
|
5
5
|
import { useFocusModality } from '../../hooks/useFocusModality'
|
|
6
|
+
import { counterStatusId, isCounterExceeded } from 'shared-utils/forms/described-by'
|
|
6
7
|
import { PktAlert } from '../alert/Alert'
|
|
7
8
|
import { PktHelptext } from '../helptext/Helptext'
|
|
8
9
|
|
|
@@ -59,7 +60,7 @@ export const PktInputWrapper = forwardRef(
|
|
|
59
60
|
children,
|
|
60
61
|
className = '',
|
|
61
62
|
hasFieldset = false,
|
|
62
|
-
role
|
|
63
|
+
role,
|
|
63
64
|
counterPosition = 'bottom',
|
|
64
65
|
size = 'medium',
|
|
65
66
|
}: IPktInputWrapper,
|
|
@@ -85,12 +86,18 @@ export const PktInputWrapper = forwardRef(
|
|
|
85
86
|
|
|
86
87
|
const tagClasses = ['pkt-tag', 'pkt-tag--small', 'pkt-tag--thin-text'].join(' ')
|
|
87
88
|
|
|
89
|
+
const counterExceeded = isCounterExceeded(counterCurrent, counterMaxLength)
|
|
88
90
|
const Counter = () =>
|
|
89
91
|
showCounter ? (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
<>
|
|
93
|
+
<div className="pkt-input__counter" aria-hidden="true">
|
|
94
|
+
{counterCurrent}
|
|
95
|
+
{counterMaxLength ? `/${counterMaxLength}` : ''}
|
|
96
|
+
</div>
|
|
97
|
+
<div className="pkt-sr-only" id={counterStatusId(forId)} aria-live="polite">
|
|
98
|
+
{counterExceeded ? `Du har brukt ${counterCurrent} av ${counterMaxLength} tegn` : ''}
|
|
99
|
+
</div>
|
|
100
|
+
</>
|
|
94
101
|
) : null
|
|
95
102
|
|
|
96
103
|
const renderTags = () => {
|
|
@@ -111,7 +118,7 @@ export const PktInputWrapper = forwardRef(
|
|
|
111
118
|
|
|
112
119
|
if (!useWrapper) {
|
|
113
120
|
return (
|
|
114
|
-
<label htmlFor={forId} className="pkt-sr-only"
|
|
121
|
+
<label htmlFor={forId} className="pkt-sr-only" id={`${forId}-label`}>
|
|
115
122
|
{label}
|
|
116
123
|
</label>
|
|
117
124
|
)
|
|
@@ -126,7 +133,7 @@ export const PktInputWrapper = forwardRef(
|
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
return (
|
|
129
|
-
<label className="pkt-inputwrapper__label" htmlFor={forId}
|
|
136
|
+
<label className="pkt-inputwrapper__label" htmlFor={forId} id={`${forId}-label`}>
|
|
130
137
|
{labelContent}
|
|
131
138
|
</label>
|
|
132
139
|
)
|
|
@@ -166,7 +173,12 @@ export const PktInputWrapper = forwardRef(
|
|
|
166
173
|
)
|
|
167
174
|
|
|
168
175
|
return (
|
|
169
|
-
<div
|
|
176
|
+
<div
|
|
177
|
+
className={wrapperClasses}
|
|
178
|
+
role={role}
|
|
179
|
+
aria-labelledby={role ? `${forId}-label` : undefined}
|
|
180
|
+
{...focusModality}
|
|
181
|
+
>
|
|
170
182
|
{hasFieldset ? (
|
|
171
183
|
<fieldset className="pkt-inputwrapper__fieldset" aria-describedby={describedBy}>
|
|
172
184
|
{content}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { ForwardedRef, forwardRef, ReactNode, SelectHTMLAttributes } from 'react'
|
|
4
4
|
|
|
5
5
|
import { PktInputWrapper } from '../inputwrapper/InputWrapper'
|
|
6
|
+
import { describedByIds } from 'shared-utils/forms/described-by'
|
|
6
7
|
|
|
7
8
|
export type { IPktSelectOption, TSelectOption } from 'shared-types/select'
|
|
8
9
|
|
|
@@ -26,6 +27,8 @@ export interface IPktSelectProps extends SelectHTMLAttributes<HTMLSelectElement>
|
|
|
26
27
|
requiredTag?: boolean
|
|
27
28
|
requiredText?: string
|
|
28
29
|
tagText?: string | null
|
|
30
|
+
useWrapper?: boolean
|
|
31
|
+
hasFieldset?: boolean
|
|
29
32
|
inputSize?: 'small' | 'medium' | 'xsmall'
|
|
30
33
|
/** @deprecated Var aldri implementert — bruk children med `<option>`-elementer. */
|
|
31
34
|
options?: never
|
|
@@ -54,47 +57,58 @@ export const PktSelect = forwardRef(
|
|
|
54
57
|
requiredTag = false,
|
|
55
58
|
requiredText,
|
|
56
59
|
tagText,
|
|
60
|
+
useWrapper = true,
|
|
61
|
+
hasFieldset,
|
|
57
62
|
inputSize = 'medium',
|
|
58
63
|
...props
|
|
59
64
|
}: IPktSelectProps,
|
|
60
65
|
ref: ForwardedRef<HTMLSelectElement>,
|
|
61
66
|
) => {
|
|
62
|
-
const
|
|
67
|
+
const outerClasses = [className, 'pkt-select'].filter(Boolean).join(' ')
|
|
63
68
|
|
|
64
69
|
return (
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
disabled={disabled}
|
|
80
|
-
inline={inline}
|
|
81
|
-
ariaDescribedby={ariaDescribedby}
|
|
82
|
-
size={inputSize}
|
|
83
|
-
>
|
|
84
|
-
<select
|
|
85
|
-
ref={ref}
|
|
86
|
-
className={`pkt-input pkt-input--${inputSize} ${fullwidth ? 'pkt-input--fullwidth' : ''}`}
|
|
87
|
-
aria-invalid={hasError}
|
|
88
|
-
aria-errormessage={`${id}-error`}
|
|
89
|
-
aria-labelledby={ariaLabelledby || undefined}
|
|
70
|
+
<div className={outerClasses}>
|
|
71
|
+
<PktInputWrapper
|
|
72
|
+
forId={`${id}-input`}
|
|
73
|
+
label={label}
|
|
74
|
+
helptext={helptext}
|
|
75
|
+
helptextDropdown={helptextDropdown}
|
|
76
|
+
helptextDropdownButton={helptextDropdownButton}
|
|
77
|
+
optionalTag={optionalTag}
|
|
78
|
+
optionalText={optionalText}
|
|
79
|
+
requiredTag={requiredTag}
|
|
80
|
+
requiredText={requiredText}
|
|
81
|
+
tagText={tagText}
|
|
82
|
+
hasError={hasError}
|
|
83
|
+
errorMessage={errorMessage}
|
|
90
84
|
disabled={disabled}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{
|
|
85
|
+
inline={inline}
|
|
86
|
+
ariaDescribedby={ariaDescribedby}
|
|
87
|
+
useWrapper={useWrapper}
|
|
88
|
+
hasFieldset={hasFieldset ?? false}
|
|
89
|
+
size={inputSize}
|
|
94
90
|
>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
<select
|
|
92
|
+
ref={ref}
|
|
93
|
+
className={`pkt-input pkt-input--${inputSize} ${fullwidth ? 'pkt-input--fullwidth' : ''}`}
|
|
94
|
+
aria-invalid={hasError}
|
|
95
|
+
aria-errormessage={hasError ? `${id}-error` : undefined}
|
|
96
|
+
aria-describedby={describedByIds({
|
|
97
|
+
id: `${id}-input`,
|
|
98
|
+
hasHelptext: !!helptext,
|
|
99
|
+
hasCounter: false,
|
|
100
|
+
ariaDescribedby,
|
|
101
|
+
})}
|
|
102
|
+
aria-labelledby={ariaLabelledby || undefined}
|
|
103
|
+
disabled={disabled}
|
|
104
|
+
id={`${id}-input`}
|
|
105
|
+
name={name || id}
|
|
106
|
+
{...props}
|
|
107
|
+
>
|
|
108
|
+
{children}
|
|
109
|
+
</select>
|
|
110
|
+
</PktInputWrapper>
|
|
111
|
+
</div>
|
|
98
112
|
)
|
|
99
113
|
},
|
|
100
114
|
)
|
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import { PktInputWrapper } from '../inputwrapper/InputWrapper'
|
|
15
15
|
import { TPktInputSize } from 'shared-types/size'
|
|
16
|
+
import { describedByIds } from 'shared-utils/forms/described-by'
|
|
16
17
|
|
|
17
18
|
export interface IPktTextarea extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
18
19
|
id: string
|
|
19
20
|
ariaDescribedby?: string
|
|
20
21
|
ariaLabelledby?: string
|
|
21
22
|
counter?: boolean
|
|
23
|
+
hasFieldset?: boolean
|
|
22
24
|
counterMaxLength?: number
|
|
23
25
|
disabled?: boolean
|
|
24
26
|
errorMessage?: string | ReactNode | ReactNode[]
|
|
@@ -56,6 +58,7 @@ export const PktTextarea = forwardRef(
|
|
|
56
58
|
ariaDescribedby,
|
|
57
59
|
ariaLabelledby,
|
|
58
60
|
counter,
|
|
61
|
+
hasFieldset,
|
|
59
62
|
counterMaxLength,
|
|
60
63
|
className,
|
|
61
64
|
disabled,
|
|
@@ -88,7 +91,8 @@ export const PktTextarea = forwardRef(
|
|
|
88
91
|
}: IPktTextarea,
|
|
89
92
|
ref: ForwardedRef<HTMLTextAreaElement>,
|
|
90
93
|
) => {
|
|
91
|
-
const
|
|
94
|
+
const showCounter = counter ?? (counterMaxLength ?? 0) > 0
|
|
95
|
+
const outerClasses = [className, 'pkt-textinput', 'pkt-textarea'].filter(Boolean).join(' ')
|
|
92
96
|
|
|
93
97
|
const inputId = `${id}-input` // id til input element og sendes inn til htmlFor/forId i InputWrapper
|
|
94
98
|
const labelId = `${inputId}-label` // id til label elementet og sendes inn til aria-labelledby i input (genereres i InputWrapper)
|
|
@@ -97,7 +101,7 @@ export const PktTextarea = forwardRef(
|
|
|
97
101
|
const [counterCurrent, setCounterCurrent] = useState(0)
|
|
98
102
|
|
|
99
103
|
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
100
|
-
if (
|
|
104
|
+
if (showCounter) {
|
|
101
105
|
setCounterCurrent(e.currentTarget?.value?.length || 0)
|
|
102
106
|
}
|
|
103
107
|
if (onChange) {
|
|
@@ -119,52 +123,60 @@ export const PktTextarea = forwardRef(
|
|
|
119
123
|
}, [value])
|
|
120
124
|
|
|
121
125
|
return (
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
disabled={disabled}
|
|
126
|
-
errorMessage={errorMessage}
|
|
127
|
-
forId={inputId}
|
|
128
|
-
hasError={hasError}
|
|
129
|
-
helptext={helptext}
|
|
130
|
-
helptextDropdown={helptextDropdown}
|
|
131
|
-
helptextDropdownButton={helptextDropdownButton}
|
|
132
|
-
inline={inline}
|
|
133
|
-
label={label}
|
|
134
|
-
optionalTag={optionalTag}
|
|
135
|
-
optionalText={optionalText}
|
|
136
|
-
requiredTag={requiredTag}
|
|
137
|
-
requiredText={requiredText}
|
|
138
|
-
tagText={tagText}
|
|
139
|
-
useWrapper={useWrapper}
|
|
140
|
-
counter={counter}
|
|
141
|
-
counterCurrent={counterCurrent}
|
|
142
|
-
counterMaxLength={counterMaxLength}
|
|
143
|
-
size={inputSize}
|
|
144
|
-
>
|
|
145
|
-
<textarea
|
|
146
|
-
ref={initRef}
|
|
147
|
-
className={`pkt-input pkt-input--${inputSize} ${fullwidth ? 'pkt-input--fullwidth' : ''} ${
|
|
148
|
-
counterMaxLength && counterCurrent > counterMaxLength ? 'pkt-input--counter-error' : ''
|
|
149
|
-
}`}
|
|
150
|
-
name={`${name || id}`}
|
|
151
|
-
id={inputId}
|
|
152
|
-
placeholder={placeholder}
|
|
126
|
+
<div className={outerClasses}>
|
|
127
|
+
<PktInputWrapper
|
|
128
|
+
ariaDescribedby={ariaDescribedby}
|
|
153
129
|
disabled={disabled}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
130
|
+
errorMessage={errorMessage}
|
|
131
|
+
forId={inputId}
|
|
132
|
+
hasError={hasError}
|
|
133
|
+
helptext={helptext}
|
|
134
|
+
helptextDropdown={helptextDropdown}
|
|
135
|
+
helptextDropdownButton={helptextDropdownButton}
|
|
136
|
+
inline={inline}
|
|
137
|
+
label={label}
|
|
138
|
+
optionalTag={optionalTag}
|
|
139
|
+
optionalText={optionalText}
|
|
140
|
+
requiredTag={requiredTag}
|
|
141
|
+
requiredText={requiredText}
|
|
142
|
+
tagText={tagText}
|
|
143
|
+
useWrapper={useWrapper}
|
|
144
|
+
counter={showCounter}
|
|
145
|
+
counterCurrent={counterCurrent}
|
|
146
|
+
counterMaxLength={counterMaxLength}
|
|
147
|
+
hasFieldset={hasFieldset ?? false}
|
|
148
|
+
size={inputSize}
|
|
149
|
+
>
|
|
150
|
+
<textarea
|
|
151
|
+
ref={initRef}
|
|
152
|
+
className={`pkt-input pkt-input--${inputSize} ${fullwidth ? 'pkt-input--fullwidth' : ''} ${
|
|
153
|
+
counterMaxLength && counterCurrent > counterMaxLength ? 'pkt-input--counter-error' : ''
|
|
154
|
+
}`}
|
|
155
|
+
name={`${name || id}`}
|
|
156
|
+
id={inputId}
|
|
157
|
+
placeholder={placeholder}
|
|
158
|
+
disabled={disabled}
|
|
159
|
+
rows={rows}
|
|
160
|
+
aria-labelledby={labelledBy}
|
|
161
|
+
aria-invalid={hasError}
|
|
162
|
+
aria-errormessage={hasError ? `${id}-error` : undefined}
|
|
163
|
+
aria-describedby={describedByIds({
|
|
164
|
+
id: inputId,
|
|
165
|
+
hasHelptext: !!helptext,
|
|
166
|
+
hasCounter: showCounter,
|
|
167
|
+
ariaDescribedby,
|
|
168
|
+
})}
|
|
169
|
+
{...props}
|
|
170
|
+
onChange={handleChange}
|
|
171
|
+
value={value}
|
|
172
|
+
autoComplete={autoComplete}
|
|
173
|
+
minLength={minLength}
|
|
174
|
+
maxLength={maxLength}
|
|
175
|
+
readOnly={readOnly}
|
|
176
|
+
data-skip-forward-testid={skipForwardTestid ? 'true' : undefined}
|
|
177
|
+
/>
|
|
178
|
+
</PktInputWrapper>
|
|
179
|
+
</div>
|
|
168
180
|
)
|
|
169
181
|
},
|
|
170
182
|
)
|