@oslokommune/punkt-react 17.1.2 → 18.0.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 +35 -0
- package/dist/components/backlink/BackLink.d.ts +3 -0
- package/dist/components/breadcrumbs/Breadcrumbs.d.ts +3 -0
- package/dist/components/calendar/types.d.ts +14 -12
- package/dist/components/checkbox/Checkbox.d.ts +5 -0
- package/dist/components/combobox/types.d.ts +5 -0
- package/dist/components/consent/strings.d.ts +1 -85
- package/dist/components/datepicker/DateTags.d.ts +2 -2
- package/dist/components/datepicker/types.d.ts +6 -3
- package/dist/components/fileupload/DropZone.d.ts +3 -0
- package/dist/components/fileupload/FileUpload.d.ts +3 -0
- package/dist/components/header/types.d.ts +5 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/inputwrapper/InputWrapper.d.ts +6 -0
- package/dist/components/radio/RadioButton.d.ts +5 -0
- package/dist/components/searchinput/SearchInput.d.ts +4 -0
- package/dist/components/select/Select.d.ts +3 -0
- package/dist/components/strings/PktStringsProvider.d.ts +21 -0
- package/dist/components/strings/strings-override.test.d.ts +1 -0
- package/dist/components/tag/Tag.d.ts +3 -0
- package/dist/components/textarea/Textarea.d.ts +3 -0
- package/dist/components/textinput/Textinput.d.ts +3 -0
- package/dist/components/timepicker/types.d.ts +3 -2
- package/dist/components/timepicker/useTimepickerState.d.ts +9 -1
- package/dist/punkt-react.cjs +1 -1
- package/dist/punkt-react.js +2969 -2749
- package/dist/shared-strings/consent.d.ts +71 -0
- package/dist/shared-strings/format.d.ts +21 -0
- package/dist/shared-strings/index.d.ts +14 -0
- package/dist/shared-strings/nb.d.ts +118 -0
- package/dist/shared-strings/resolve.d.ts +12 -0
- package/dist/shared-strings/store.d.ts +27 -0
- package/dist/shared-strings/types.d.ts +29 -0
- package/dist/shared-types/fileupload.d.ts +0 -27
- package/dist/shared-types/index.d.ts +1 -2
- package/dist/shared-utils/combobox/index.d.ts +1 -0
- package/dist/shared-utils/combobox/input-utils.d.ts +2 -1
- package/dist/shared-utils/combobox/messages.d.ts +7 -0
- package/dist/shared-utils/combobox/option-utils.d.ts +2 -1
- package/dist/shared-utils/combobox/selection-manager.d.ts +3 -2
- package/package.json +4 -4
- package/src/components/backlink/BackLink.tsx +11 -2
- package/src/components/breadcrumbs/Breadcrumbs.tsx +8 -2
- package/src/components/calendar/Calendar.core.test.tsx +7 -5
- package/src/components/calendar/types.ts +14 -34
- package/src/components/calendar/useCalendarState.ts +16 -2
- package/src/components/checkbox/Checkbox.tsx +20 -4
- package/src/components/combobox/Combobox.tsx +1 -0
- package/src/components/combobox/Listbox.tsx +4 -2
- package/src/components/combobox/types.ts +6 -0
- package/src/components/combobox/useComboboxState.ts +15 -8
- package/src/components/consent/strings.ts +1 -125
- package/src/components/datepicker/DateTags.tsx +4 -3
- package/src/components/datepicker/Datepicker.accessibility.test.tsx +2 -2
- package/src/components/datepicker/Datepicker.tsx +1 -0
- package/src/components/datepicker/DatepickerInputs.tsx +6 -6
- package/src/components/datepicker/types.ts +6 -3
- package/src/components/datepicker/useDatepickerState.ts +4 -5
- package/src/components/fileupload/DropZone.tsx +20 -15
- package/src/components/fileupload/FileUpload.test.tsx +37 -0
- package/src/components/fileupload/FileUpload.tsx +41 -8
- package/src/components/header/HeaderGlobal.tsx +5 -2
- package/src/components/header/HeaderService.tsx +12 -7
- package/src/components/header/types.ts +6 -0
- package/src/components/index.ts +10 -0
- package/src/components/inputwrapper/InputWrapper.tsx +28 -6
- package/src/components/radio/RadioButton.tsx +18 -2
- package/src/components/searchinput/SearchInput.tsx +13 -3
- package/src/components/select/Select.tsx +5 -0
- package/src/components/strings/PktStringsProvider.tsx +38 -0
- package/src/components/strings/strings-override.test.tsx +130 -0
- package/src/components/tag/Tag.tsx +13 -2
- package/src/components/textarea/Textarea.tsx +5 -0
- package/src/components/textinput/Textinput.tsx +5 -0
- package/src/components/timepicker/Timepicker.tsx +1 -0
- package/src/components/timepicker/types.ts +3 -2
- package/src/components/timepicker/useTimepickerState.ts +20 -22
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
useEffect,
|
|
9
9
|
useRef,
|
|
10
10
|
} from 'react'
|
|
11
|
+
import { usePktStrings } from '../../hooks/usePktStrings'
|
|
12
|
+
import type { TPktStringsOverride } from 'shared-strings'
|
|
11
13
|
|
|
12
14
|
export interface IPktCheckbox extends InputHTMLAttributes<HTMLInputElement> {
|
|
13
15
|
id: string
|
|
@@ -24,9 +26,13 @@ export interface IPktCheckbox extends InputHTMLAttributes<HTMLInputElement> {
|
|
|
24
26
|
hideLabel?: boolean
|
|
25
27
|
labelPosition?: 'right' | 'left'
|
|
26
28
|
optionalTag?: boolean
|
|
29
|
+
/** @deprecated Bruk `strings={{ forms: { optional: '…' } }}` eller `setPktStrings()`. */
|
|
27
30
|
optionalText?: string
|
|
28
31
|
requiredTag?: boolean
|
|
32
|
+
/** @deprecated Bruk `strings={{ forms: { required: '…' } }}` eller `setPktStrings()`. */
|
|
29
33
|
requiredText?: string
|
|
34
|
+
/** Overstyrer tekster for denne komponenten. */
|
|
35
|
+
strings?: TPktStringsOverride
|
|
30
36
|
tagText?: string | null
|
|
31
37
|
onChange?: ChangeEventHandler<HTMLInputElement>
|
|
32
38
|
}
|
|
@@ -48,14 +54,16 @@ export const PktCheckbox = forwardRef(
|
|
|
48
54
|
checked,
|
|
49
55
|
indeterminate,
|
|
50
56
|
optionalTag,
|
|
51
|
-
optionalText
|
|
57
|
+
optionalText,
|
|
52
58
|
requiredTag,
|
|
53
|
-
requiredText
|
|
59
|
+
requiredText,
|
|
60
|
+
strings,
|
|
54
61
|
tagText,
|
|
55
62
|
...props
|
|
56
63
|
}: IPktCheckbox,
|
|
57
64
|
ref: ForwardedRef<HTMLInputElement>,
|
|
58
65
|
): ReactElement => {
|
|
66
|
+
const s = usePktStrings(['forms'], strings)
|
|
59
67
|
const internalRef = useRef<HTMLInputElement>(null)
|
|
60
68
|
|
|
61
69
|
useEffect(() => {
|
|
@@ -97,8 +105,16 @@ export const PktCheckbox = forwardRef(
|
|
|
97
105
|
return (
|
|
98
106
|
<>
|
|
99
107
|
{tagText && <span className={tagClasses + ` pkt-tag--gray`}>{tagText}</span>}
|
|
100
|
-
{optionalTag &&
|
|
101
|
-
|
|
108
|
+
{optionalTag && (
|
|
109
|
+
<span className={tagClasses + ` pkt-tag--blue-light`}>
|
|
110
|
+
{optionalText ?? s.forms.optional}
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
{requiredTag && (
|
|
114
|
+
<span className={tagClasses + ` pkt-tag--beige`}>
|
|
115
|
+
{requiredText ?? s.forms.required}
|
|
116
|
+
</span>
|
|
117
|
+
)}
|
|
102
118
|
</>
|
|
103
119
|
)
|
|
104
120
|
}
|
|
@@ -27,6 +27,7 @@ export const PktCombobox = forwardRef<HTMLDivElement, IPktCombobox>((props, ref)
|
|
|
27
27
|
return (
|
|
28
28
|
<div className={outerClasses} ref={state.wrapperRef} id={state.id || undefined}>
|
|
29
29
|
<PktInputWrapper
|
|
30
|
+
strings={state.strings}
|
|
30
31
|
forId={wrapperForId}
|
|
31
32
|
hasFieldset={props.hasFieldset ?? !hasTextInput}
|
|
32
33
|
label={state.label || ''}
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import type { IComboboxState } from './types'
|
|
4
4
|
import { PktIcon } from '../icon/Icon'
|
|
5
|
+
import { usePktStrings } from '../../hooks/usePktStrings'
|
|
5
6
|
|
|
6
7
|
interface IListboxProps {
|
|
7
8
|
state: IComboboxState
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export const Listbox = ({ state }: IListboxProps) => {
|
|
12
|
+
const s = usePktStrings(['listbox'], state.strings)
|
|
11
13
|
const {
|
|
12
14
|
listboxId,
|
|
13
15
|
label,
|
|
@@ -59,9 +61,9 @@ export const Listbox = ({ state }: IListboxProps) => {
|
|
|
59
61
|
<input
|
|
60
62
|
className="pkt-txt-16-light"
|
|
61
63
|
type="text"
|
|
62
|
-
aria-label=
|
|
64
|
+
aria-label={s.listbox.searchLabel}
|
|
63
65
|
form=""
|
|
64
|
-
placeholder={searchPlaceholder
|
|
66
|
+
placeholder={searchPlaceholder ?? s.listbox.searchPlaceholder}
|
|
65
67
|
onChange={handleSearchInput}
|
|
66
68
|
onKeyDown={handleSearchKeydown}
|
|
67
69
|
value={searchValue}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import type { TPktStringsOverride } from 'shared-strings'
|
|
4
|
+
|
|
3
5
|
import type {
|
|
4
6
|
ChangeEvent,
|
|
5
7
|
ChangeEventHandler,
|
|
@@ -52,6 +54,8 @@ export interface IPktCombobox {
|
|
|
52
54
|
useWrapper?: boolean
|
|
53
55
|
inline?: boolean
|
|
54
56
|
counter?: boolean
|
|
57
|
+
/** Overstyrer tekster for denne komponenten. */
|
|
58
|
+
strings?: TPktStringsOverride
|
|
55
59
|
hasFieldset?: boolean
|
|
56
60
|
inputSize?: 'small' | 'medium' | 'xsmall'
|
|
57
61
|
|
|
@@ -65,6 +69,8 @@ export interface IPktCombobox {
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
export interface IComboboxState {
|
|
72
|
+
/** Tekstoverstyring sendt inn på komponenten. */
|
|
73
|
+
strings?: TPktStringsOverride
|
|
68
74
|
// Identity
|
|
69
75
|
id: string
|
|
70
76
|
inputId: string
|
|
@@ -27,9 +27,11 @@ import {
|
|
|
27
27
|
getInputKeyAction,
|
|
28
28
|
getInputValueAction,
|
|
29
29
|
getSingleValueForInput,
|
|
30
|
+
type TComboboxMessageKey,
|
|
30
31
|
} from 'shared-utils/combobox'
|
|
31
32
|
|
|
32
33
|
import type { IPktCombobox, IComboboxState } from './types'
|
|
34
|
+
import { usePktStrings } from '../../hooks/usePktStrings'
|
|
33
35
|
|
|
34
36
|
// Options sync helper (React-specific: immutable, returns new array)
|
|
35
37
|
function syncOptionsWithValues(options: IPktComboboxOption[], values: string[]): IPktComboboxOption[] {
|
|
@@ -80,6 +82,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
80
82
|
onChange,
|
|
81
83
|
onValueChange,
|
|
82
84
|
children,
|
|
85
|
+
strings,
|
|
83
86
|
} = props
|
|
84
87
|
|
|
85
88
|
// Identity
|
|
@@ -177,13 +180,16 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
177
180
|
const [isOpen, setIsOpen] = useState(false)
|
|
178
181
|
const [searchValue, setSearchValue] = useState('')
|
|
179
182
|
const [inputValue, setInputValue] = useState('')
|
|
180
|
-
const [
|
|
183
|
+
const [userInfoMessageKey, setUserInfoMessage] = useState<TComboboxMessageKey>('')
|
|
181
184
|
const [addValueText, setAddValueText] = useState<string | null>(null)
|
|
182
185
|
const [inputFocus, setInputFocus] = useState(false)
|
|
183
186
|
const [editingSingleValue, setEditingSingleValue] = useState(false)
|
|
184
187
|
const [focusedTagIndex, setFocusedTagIndex] = useState<number>(-1)
|
|
185
188
|
const suppressNextOpenRef = useRef(false)
|
|
186
189
|
|
|
190
|
+
const catalog = usePktStrings(['combobox'], strings)
|
|
191
|
+
const userInfoMessage = userInfoMessageKey ? catalog.combobox[userInfoMessageKey] : ''
|
|
192
|
+
|
|
187
193
|
// Derived state
|
|
188
194
|
const maxIsReached = isMaxSelectionReached(values.length, maxlength ?? null)
|
|
189
195
|
// Telleren vises av seg selv når det er noe å telle, men kan slås av eksplisitt
|
|
@@ -250,7 +256,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
250
256
|
if (!optValue) return
|
|
251
257
|
if (values.includes(optValue)) return
|
|
252
258
|
if (multiple && isMaxSelectionReached(values.length, maxlength ?? null)) {
|
|
253
|
-
setUserInfoMessage('
|
|
259
|
+
setUserInfoMessage('maxSelected')
|
|
254
260
|
return
|
|
255
261
|
}
|
|
256
262
|
const newValues = multiple ? [...values, optValue] : [optValue]
|
|
@@ -346,7 +352,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
346
352
|
|
|
347
353
|
let shouldOptionsBeOpen = false
|
|
348
354
|
let shouldResetInput = true
|
|
349
|
-
let newUserInfoMessage = ''
|
|
355
|
+
let newUserInfoMessage: TComboboxMessageKey = ''
|
|
350
356
|
let newSearchValue = ''
|
|
351
357
|
// Track the resulting single value for resetInput (avoids stale closure reads)
|
|
352
358
|
let resultingSingleValue: string | undefined
|
|
@@ -356,7 +362,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
356
362
|
// Not in list + allowUserInput
|
|
357
363
|
if (!isInOption && allowUserInput && !isEmpty) {
|
|
358
364
|
addNewUserValue(val)
|
|
359
|
-
newUserInfoMessage = '
|
|
365
|
+
newUserInfoMessage = 'valueAdded'
|
|
360
366
|
shouldOptionsBeOpen = isMultiple
|
|
361
367
|
if (isSingle) resultingSingleValue = val || ''
|
|
362
368
|
}
|
|
@@ -367,7 +373,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
367
373
|
}
|
|
368
374
|
shouldResetInput = false
|
|
369
375
|
shouldOptionsBeOpen = true
|
|
370
|
-
newUserInfoMessage = '
|
|
376
|
+
newUserInfoMessage = 'noResults'
|
|
371
377
|
}
|
|
372
378
|
// Already selected — deselect
|
|
373
379
|
else if (isSelected) {
|
|
@@ -399,14 +405,14 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
399
405
|
}
|
|
400
406
|
// Multi at max
|
|
401
407
|
else if (isMultiple && isMaxItemsReached) {
|
|
402
|
-
newUserInfoMessage = '
|
|
408
|
+
newUserInfoMessage = 'maxSelected'
|
|
403
409
|
shouldResetInput = false
|
|
404
410
|
newSearchValue = val || ''
|
|
405
411
|
}
|
|
406
412
|
// Fallback
|
|
407
413
|
else {
|
|
408
414
|
if (isSingle) removeAllSelected()
|
|
409
|
-
newUserInfoMessage = '
|
|
415
|
+
newUserInfoMessage = 'noValidValue'
|
|
410
416
|
shouldResetInput = false
|
|
411
417
|
shouldOptionsBeOpen = true
|
|
412
418
|
newSearchValue = val || ''
|
|
@@ -606,7 +612,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
606
612
|
// Just reset the input and keep the "already selected" message.
|
|
607
613
|
if (input && values.includes(input)) {
|
|
608
614
|
resetInput(true)
|
|
609
|
-
setUserInfoMessage('
|
|
615
|
+
setUserInfoMessage('alreadySelected')
|
|
610
616
|
break
|
|
611
617
|
}
|
|
612
618
|
toggleValue(input)
|
|
@@ -1077,6 +1083,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
1077
1083
|
// UI state
|
|
1078
1084
|
isOpen,
|
|
1079
1085
|
userInfoMessage,
|
|
1086
|
+
strings,
|
|
1080
1087
|
addValueText,
|
|
1081
1088
|
maxIsReached,
|
|
1082
1089
|
editingSingleValue,
|
|
@@ -1,125 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
i18n: {
|
|
3
|
-
nb: {
|
|
4
|
-
contentPresentation: {
|
|
5
|
-
title: 'Oslo kommune bruker informasjonskapsler',
|
|
6
|
-
description: [
|
|
7
|
-
'For at nettstedet skal fungere og være trygt, bruker Oslo kommune informasjonskapsler. Noen er teknisk nødvendige, mens andre sikrer ulik funksjonalitet.',
|
|
8
|
-
'Godtar du alle informasjonskapsler, tillater du også at vi samler inn data om statistikk og brukeradferd. Da hjelper du oss med å lage et bedre nettsted uten at du trenger å dele noe personlig informasjon med oss.',
|
|
9
|
-
],
|
|
10
|
-
buttons: {
|
|
11
|
-
accept: 'Godta alle',
|
|
12
|
-
reject: 'Kun nødvendige',
|
|
13
|
-
settings: 'Innstillinger for informasjonskapsler',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
contentSettings: {
|
|
17
|
-
title: 'Innstillinger for informasjonskapsler',
|
|
18
|
-
description: [
|
|
19
|
-
'Her kan du velge hvilke typer informasjonskapsler du vil tillate. Tillatelsen gjelder i 90 dager. Husk at nødvendige informasjonskapsler ikke kan velges bort.',
|
|
20
|
-
'Du kan når som helst endre innstillingene og finne mer informasjon nederst på nettstedet under «Innstillinger for informasjonskapsler» og «Personvern og informasjonskapsler».',
|
|
21
|
-
],
|
|
22
|
-
buttons: {
|
|
23
|
-
back: 'Tilbake',
|
|
24
|
-
save: 'Lagre innstillinger',
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
en: {
|
|
29
|
-
contentPresentation: {
|
|
30
|
-
title: 'Before you visit Oslo kommune ...',
|
|
31
|
-
description: [
|
|
32
|
-
'This website uses cookies to make improvements. In this context, we need your consent to measure the traffic on the website in relation to statistics and feedback.',
|
|
33
|
-
'To read more about what we use cookies for, go to our privacy declaration which you will find at the bottom of our websites.',
|
|
34
|
-
],
|
|
35
|
-
buttons: {
|
|
36
|
-
accept: 'Yes, I accept',
|
|
37
|
-
reject: 'Only necessary',
|
|
38
|
-
settings: 'Go to settings',
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
contentSettings: {
|
|
42
|
-
title: 'Her kan du aktivt velge mellom ulike informasjonskapsler',
|
|
43
|
-
description: [
|
|
44
|
-
'For å lese mer om hva vi bruker informasjonskapsler til gå til vår personvernserklering som du finner på våre nettsider',
|
|
45
|
-
],
|
|
46
|
-
buttons: {
|
|
47
|
-
back: 'Back',
|
|
48
|
-
save: 'Save settings',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
cookies: [
|
|
54
|
-
{
|
|
55
|
-
name: 'statistics',
|
|
56
|
-
defaultValue: null,
|
|
57
|
-
i18n: {
|
|
58
|
-
nb: {
|
|
59
|
-
title: 'Statistikk og analyse',
|
|
60
|
-
description:
|
|
61
|
-
'Jeg godtar at Oslo kommune bruker informasjonskapsler til å innhente data om statistikk og brukeradferd.',
|
|
62
|
-
buttons: {
|
|
63
|
-
yes: 'Ja',
|
|
64
|
-
no: 'Nei',
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
en: {
|
|
68
|
-
title: 'Statistics and analytics',
|
|
69
|
-
description: 'Select your preference for statistics and analytics',
|
|
70
|
-
buttons: {
|
|
71
|
-
yes: 'Yes',
|
|
72
|
-
no: 'No',
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: 'survey',
|
|
79
|
-
defaultValue: null,
|
|
80
|
-
i18n: {
|
|
81
|
-
nb: {
|
|
82
|
-
title: 'Skriftlige tilbakemeldinger',
|
|
83
|
-
description:
|
|
84
|
-
'Jeg godtar at Oslo kommune bruker informasjonskapsler til å vise responsfelt og registrere tilbakemeldinger.',
|
|
85
|
-
buttons: {
|
|
86
|
-
yes: 'Ja',
|
|
87
|
-
no: 'Nei',
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
en: {
|
|
91
|
-
title: 'Survey',
|
|
92
|
-
description: 'Select your preference for survey',
|
|
93
|
-
buttons: {
|
|
94
|
-
yes: 'Yes',
|
|
95
|
-
no: 'No',
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: 'functional',
|
|
102
|
-
defaultValue: true,
|
|
103
|
-
i18n: {
|
|
104
|
-
nb: {
|
|
105
|
-
title: 'Funksjonelle informasjonskapsler',
|
|
106
|
-
description:
|
|
107
|
-
'Jeg godtar at Oslo kommune bruker informasjonskapsler for å gjøre ulike funksjoner på nettstedet mulige (for eksempel skjemaer og barnehagevelger).',
|
|
108
|
-
buttons: {
|
|
109
|
-
yes: 'Ja',
|
|
110
|
-
no: 'Nei',
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
en: {
|
|
114
|
-
title: 'Functional cookies',
|
|
115
|
-
description:
|
|
116
|
-
'For the website to function optimally, we recommend that you accept these cookies',
|
|
117
|
-
buttons: {
|
|
118
|
-
yes: 'Yes',
|
|
119
|
-
no: 'No',
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
}
|
|
1
|
+
export { consentStrings } from 'shared-strings/consent'
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { sortDateStrings, fromISOtoLocal } from 'shared-utils/date-utils'
|
|
4
|
-
import type { IDatepickerStrings } from 'shared-types/datepicker'
|
|
5
4
|
import { PktTag } from '../tag/Tag'
|
|
5
|
+
import { usePktStrings } from '../../hooks/usePktStrings'
|
|
6
|
+
import type { TPktStringsOverride } from 'shared-strings'
|
|
6
7
|
|
|
7
8
|
interface DateTagsProps {
|
|
8
9
|
dates: string[]
|
|
9
10
|
dateformat: string
|
|
10
11
|
idBase: string
|
|
11
|
-
strings?:
|
|
12
|
+
strings?: TPktStringsOverride
|
|
12
13
|
onDateRemoved: (date: string) => void
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -20,7 +21,7 @@ export const DateTags = ({
|
|
|
20
21
|
onDateRemoved,
|
|
21
22
|
}: DateTagsProps) => {
|
|
22
23
|
const sorted = sortDateStrings(dates)
|
|
23
|
-
const deleteLabel =
|
|
24
|
+
const deleteLabel = usePktStrings(['datepicker'], strings).datepicker.deleteDate
|
|
24
25
|
|
|
25
26
|
return (
|
|
26
27
|
<div className="pkt-date-tags pkt-datepicker__tags" aria-live="polite">
|
|
@@ -256,7 +256,7 @@ describe('PktDatepicker', () => {
|
|
|
256
256
|
test('uses custom strings for calendar button', () => {
|
|
257
257
|
const { container } = createDatepickerTest({
|
|
258
258
|
strings: {
|
|
259
|
-
|
|
259
|
+
datepicker: { openPicker: 'Open calendar' },
|
|
260
260
|
},
|
|
261
261
|
})
|
|
262
262
|
|
|
@@ -270,7 +270,7 @@ describe('PktDatepicker', () => {
|
|
|
270
270
|
showRangeLabels: true,
|
|
271
271
|
strings: {
|
|
272
272
|
generic: { from: 'From', to: 'To' },
|
|
273
|
-
|
|
273
|
+
datepicker: { openPicker: 'Open calendar' },
|
|
274
274
|
},
|
|
275
275
|
})
|
|
276
276
|
|
|
@@ -24,6 +24,7 @@ export const PktDatepicker = forwardRef<HTMLDivElement, IPktDatepicker>((props,
|
|
|
24
24
|
return (
|
|
25
25
|
<div className={outerClasses} id={state.id || undefined}>
|
|
26
26
|
<PktInputWrapper
|
|
27
|
+
strings={state.stringsOverride}
|
|
27
28
|
ref={state.wrapperRef}
|
|
28
29
|
ariaDescribedby={props.ariaDescribedby}
|
|
29
30
|
forId={state.inputId}
|
|
@@ -64,10 +64,10 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
64
64
|
type="button"
|
|
65
65
|
onClick={toggleCalendar}
|
|
66
66
|
disabled={disabled}
|
|
67
|
-
aria-label={strings.
|
|
67
|
+
aria-label={strings.datepicker.openPicker}
|
|
68
68
|
>
|
|
69
69
|
<PktIcon name="calendar" />
|
|
70
|
-
<span className="pkt-btn__text">{strings.
|
|
70
|
+
<span className="pkt-btn__text">{strings.datepicker.openPicker}</span>
|
|
71
71
|
</button>
|
|
72
72
|
)
|
|
73
73
|
|
|
@@ -113,7 +113,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
113
113
|
return (
|
|
114
114
|
<div className={datepickerInputsClasses}>
|
|
115
115
|
<div className="pkt-input__container">
|
|
116
|
-
{showRangeLabels && <div className="pkt-input-prefix">{strings.generic
|
|
116
|
+
{showRangeLabels && <div className="pkt-input-prefix">{strings.generic.from}</div>}
|
|
117
117
|
<input
|
|
118
118
|
{...restProps}
|
|
119
119
|
ref={inputRef}
|
|
@@ -128,7 +128,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
128
128
|
readOnly={readOnly || (isIOSDevice && !readOnly)}
|
|
129
129
|
disabled={disabled}
|
|
130
130
|
required={required}
|
|
131
|
-
aria-label={`${label} ${strings.generic
|
|
131
|
+
aria-label={`${label} ${strings.generic.from}`}
|
|
132
132
|
aria-describedby={helptext ? `${id}-helptext` : undefined}
|
|
133
133
|
aria-invalid={hasError}
|
|
134
134
|
aria-errormessage={hasError ? `${id}-error` : undefined}
|
|
@@ -140,7 +140,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
140
140
|
onChange={handleRangeFromChange}
|
|
141
141
|
/>
|
|
142
142
|
<div className={rangeLabelClasses} id={`${id}-to-label`}>
|
|
143
|
-
{strings.generic
|
|
143
|
+
{strings.generic.to}
|
|
144
144
|
</div>
|
|
145
145
|
{!showRangeLabels && <div className="pkt-input-separator">–</div>}
|
|
146
146
|
<input
|
|
@@ -154,7 +154,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
154
154
|
max={maxStr}
|
|
155
155
|
readOnly={readOnly || (isIOSDevice && !readOnly)}
|
|
156
156
|
disabled={disabled}
|
|
157
|
-
aria-label={`${label} ${strings.generic
|
|
157
|
+
aria-label={`${label} ${strings.generic.to}`}
|
|
158
158
|
aria-invalid={hasError}
|
|
159
159
|
onClick={handleInputClick}
|
|
160
160
|
onTouchEnd={handleInputTouchEnd}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { TPktStrings, TPktStringsOverride } from 'shared-strings'
|
|
1
2
|
import type { ChangeEvent, InputHTMLAttributes, ReactNode } from 'react'
|
|
2
|
-
import type { IDatepickerStrings } from 'shared-types/datepicker'
|
|
3
3
|
import type { TPktInputSize } from 'shared-types/size'
|
|
4
4
|
|
|
5
5
|
export interface IPktDatepicker extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
|
|
@@ -42,7 +42,8 @@ export interface IPktDatepicker extends Omit<InputHTMLAttributes<HTMLInputElemen
|
|
|
42
42
|
useWrapper?: boolean
|
|
43
43
|
|
|
44
44
|
// Localization
|
|
45
|
-
|
|
45
|
+
/** Overstyrer tekster for denne komponenten. */
|
|
46
|
+
strings?: TPktStringsOverride
|
|
46
47
|
|
|
47
48
|
// Events
|
|
48
49
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
|
@@ -103,7 +104,9 @@ export interface IDatepickerState {
|
|
|
103
104
|
// Derived state
|
|
104
105
|
inputType: string
|
|
105
106
|
isIOSDevice: boolean
|
|
106
|
-
strings:
|
|
107
|
+
strings: Pick<TPktStrings, 'generic' | 'datepicker' | 'validation'>
|
|
108
|
+
/** Rå tekstoverstyring, til videresending til underkomponenter. */
|
|
109
|
+
stringsOverride?: TPktStringsOverride
|
|
107
110
|
inputClasses: string
|
|
108
111
|
buttonClasses: string
|
|
109
112
|
rangeLabelClasses: string
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import { defaultSingleStrings, defaultRangeStrings } from 'shared-types/datepicker'
|
|
3
2
|
import type { TPktInputSize } from 'shared-types/size'
|
|
4
3
|
import { valueToArray } from 'shared-utils/value-utils'
|
|
5
4
|
import { isIOS } from 'shared-utils/device-utils'
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
} from 'shared-utils/datepicker-utils'
|
|
16
15
|
|
|
17
16
|
import type { IPktDatepicker, IDatepickerState } from './types'
|
|
17
|
+
import { usePktStrings } from '../../hooks/usePktStrings'
|
|
18
18
|
|
|
19
19
|
export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRef<HTMLDivElement>): IDatepickerState {
|
|
20
20
|
const {
|
|
@@ -104,10 +104,8 @@ export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRe
|
|
|
104
104
|
// Derived state
|
|
105
105
|
const inputType = useMemo(() => getDatepickerInputType(), [])
|
|
106
106
|
const isIOSDevice = useMemo(() => isIOS(), [])
|
|
107
|
-
const strings =
|
|
108
|
-
|
|
109
|
-
[stringsProp, range],
|
|
110
|
-
)
|
|
107
|
+
const strings = usePktStrings(['generic', 'datepicker', 'validation'], stringsProp)
|
|
108
|
+
const stringsOverride = stringsProp
|
|
111
109
|
|
|
112
110
|
const minStr = typeof min === 'string' ? min : min !== undefined ? String(min) : undefined
|
|
113
111
|
const maxStr = typeof max === 'string' ? max : max !== undefined ? String(max) : undefined
|
|
@@ -507,6 +505,7 @@ export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRe
|
|
|
507
505
|
inputType,
|
|
508
506
|
isIOSDevice,
|
|
509
507
|
strings,
|
|
508
|
+
stringsOverride,
|
|
510
509
|
inputClasses,
|
|
511
510
|
buttonClasses,
|
|
512
511
|
rangeLabelClasses,
|
|
@@ -12,10 +12,11 @@ import {
|
|
|
12
12
|
useState,
|
|
13
13
|
} from 'react'
|
|
14
14
|
|
|
15
|
-
import { defaultFileUploadStrings } from 'shared-types'
|
|
16
15
|
|
|
17
16
|
import { PktIcon } from '../icon/Icon'
|
|
18
17
|
import { FileItem, TFileItemList, TUploadStrategy } from './types'
|
|
18
|
+
import { usePktStrings } from '../../hooks/usePktStrings'
|
|
19
|
+
import type { TPktStringsOverride } from 'shared-strings'
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Props for the internal `DropZone` building block.
|
|
@@ -27,6 +28,8 @@ import { FileItem, TFileItemList, TUploadStrategy } from './types'
|
|
|
27
28
|
*/
|
|
28
29
|
interface IDropZoneProps
|
|
29
30
|
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'checked' | 'size' | 'type' | 'value' | 'onChange' | 'width'> {
|
|
31
|
+
/** Overstyrer tekster for denne komponenten. */
|
|
32
|
+
strings?: TPktStringsOverride
|
|
30
33
|
/** Called with `FileItem`s created from dropped/selected files. */
|
|
31
34
|
onFilesAdded?: (files: TFileItemList) => void
|
|
32
35
|
/** Used for the native input `id` and related label/ARIA wiring. */
|
|
@@ -63,6 +66,7 @@ export const DropZone = forwardRef<HTMLInputElement, IDropZoneProps>(
|
|
|
63
66
|
isThumbnailView = false,
|
|
64
67
|
disabled = false,
|
|
65
68
|
hasVisibleLabel = false,
|
|
69
|
+
strings,
|
|
66
70
|
...inputProps
|
|
67
71
|
}: IDropZoneProps,
|
|
68
72
|
forwardedRef,
|
|
@@ -86,33 +90,34 @@ export const DropZone = forwardRef<HTMLInputElement, IDropZoneProps>(
|
|
|
86
90
|
useImperativeHandle(forwardedRef, () => fileInputRef.current! as HTMLInputElement)
|
|
87
91
|
|
|
88
92
|
const [isDragActive, setIsDragActive] = useState(false)
|
|
93
|
+
const s = usePktStrings(['fileupload'], strings)
|
|
89
94
|
|
|
90
95
|
const uiTexts = useMemo(() => {
|
|
91
96
|
if (isThumbnailView) {
|
|
92
97
|
return multiple
|
|
93
98
|
? {
|
|
94
|
-
dragInactive:
|
|
95
|
-
dragActive:
|
|
96
|
-
openFileDialog:
|
|
99
|
+
dragInactive: s.fileupload.dropZoneDragMultipleThumbnail,
|
|
100
|
+
dragActive: s.fileupload.dropZoneDragActiveMultipleThumbnail,
|
|
101
|
+
openFileDialog: s.fileupload.dropZoneOpenFileDialogMultipleThumbnail,
|
|
97
102
|
}
|
|
98
103
|
: {
|
|
99
|
-
dragInactive:
|
|
100
|
-
dragActive:
|
|
101
|
-
openFileDialog:
|
|
104
|
+
dragInactive: s.fileupload.dropZoneDragSingleThumbnail,
|
|
105
|
+
dragActive: s.fileupload.dropZoneDragActiveSingleThumbnail,
|
|
106
|
+
openFileDialog: s.fileupload.dropZoneOpenFileDialogSingleThumbnail,
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
return multiple
|
|
105
110
|
? {
|
|
106
|
-
dragInactive:
|
|
107
|
-
dragActive:
|
|
108
|
-
openFileDialog:
|
|
111
|
+
dragInactive: s.fileupload.dropZoneDragMultiple,
|
|
112
|
+
dragActive: s.fileupload.dropZoneDragActiveMultiple,
|
|
113
|
+
openFileDialog: s.fileupload.dropZoneOpenFileDialogMultiple,
|
|
109
114
|
}
|
|
110
115
|
: {
|
|
111
|
-
dragInactive:
|
|
112
|
-
dragActive:
|
|
113
|
-
openFileDialog:
|
|
116
|
+
dragInactive: s.fileupload.dropZoneDragSingle,
|
|
117
|
+
dragActive: s.fileupload.dropZoneDragActiveSingle,
|
|
118
|
+
openFileDialog: s.fileupload.dropZoneOpenFileDialogSingle,
|
|
114
119
|
}
|
|
115
|
-
}, [multiple, isThumbnailView])
|
|
120
|
+
}, [multiple, isThumbnailView, s])
|
|
116
121
|
|
|
117
122
|
const populateNativeFileInput = useCallback(
|
|
118
123
|
(fileItemList: TFileItemList) => {
|
|
@@ -240,7 +245,7 @@ export const DropZone = forwardRef<HTMLInputElement, IDropZoneProps>(
|
|
|
240
245
|
</p>
|
|
241
246
|
{acceptedFormatsReadableString ? (
|
|
242
247
|
<p className={'pkt-fileupload__drop-zone__placeholder__formats'}>
|
|
243
|
-
{
|
|
248
|
+
{s.fileupload.supportedFormatsPrefix} {acceptedFormatsReadableString}
|
|
244
249
|
</p>
|
|
245
250
|
) : null}
|
|
246
251
|
</div>
|
|
@@ -152,6 +152,43 @@ describe('PktFileUpload', () => {
|
|
|
152
152
|
expectVisibleFilename('file2.docx')
|
|
153
153
|
expectVisibleFilename('file3.png')
|
|
154
154
|
})
|
|
155
|
+
|
|
156
|
+
it('should use drop zone text from the strings prop', () => {
|
|
157
|
+
render(
|
|
158
|
+
<PktFileUpload
|
|
159
|
+
multiple
|
|
160
|
+
name="pktFileUpload"
|
|
161
|
+
strings={{
|
|
162
|
+
fileupload: {
|
|
163
|
+
dropZoneDragMultiple: 'Dra dokumentene hit eller',
|
|
164
|
+
dropZoneOpenFileDialogMultiple: 'bla i filene',
|
|
165
|
+
},
|
|
166
|
+
}}
|
|
167
|
+
/>,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
expect(screen.getByText(/Dra dokumentene hit eller/)).toBeInTheDocument()
|
|
171
|
+
expect(screen.getByText('bla i filene')).toBeInTheDocument()
|
|
172
|
+
expect(screen.queryByText('velg filer')).not.toBeInTheDocument()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should use overridden drag-active drop zone text', () => {
|
|
176
|
+
const { container } = render(
|
|
177
|
+
<PktFileUpload
|
|
178
|
+
multiple
|
|
179
|
+
name="pktFileUpload"
|
|
180
|
+
strings={{
|
|
181
|
+
fileupload: {
|
|
182
|
+
dropZoneDragActiveMultiple: 'Slipp dokumentene her',
|
|
183
|
+
},
|
|
184
|
+
}}
|
|
185
|
+
/>,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
fireEvent.dragOver(container.querySelector('.pkt-fileupload__drop-zone') as HTMLElement)
|
|
189
|
+
|
|
190
|
+
expect(screen.getByText(/Slipp dokumentene her/)).toBeInTheDocument()
|
|
191
|
+
})
|
|
155
192
|
})
|
|
156
193
|
|
|
157
194
|
describe('File selection via dialog', () => {
|