@oslokommune/punkt-react 17.1.3 → 18.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/components/accordion/Accordion.d.ts +2 -12
  3. package/dist/components/accordion/AccordionItem.d.ts +2 -8
  4. package/dist/components/alert/Alert.d.ts +4 -3
  5. package/dist/components/backlink/BackLink.d.ts +3 -10
  6. package/dist/components/breadcrumbs/Breadcrumbs.d.ts +7 -13
  7. package/dist/components/calendar/types.d.ts +14 -12
  8. package/dist/components/checkbox/Checkbox.d.ts +5 -0
  9. package/dist/components/combobox/types.d.ts +5 -0
  10. package/dist/components/consent/strings.d.ts +1 -85
  11. package/dist/components/datepicker/DateTags.d.ts +2 -2
  12. package/dist/components/datepicker/types.d.ts +6 -3
  13. package/dist/components/fileupload/DropZone.d.ts +3 -0
  14. package/dist/components/fileupload/FileUpload.d.ts +9 -61
  15. package/dist/components/header/types.d.ts +5 -0
  16. package/dist/components/index.d.ts +4 -0
  17. package/dist/components/inputwrapper/InputWrapper.d.ts +6 -0
  18. package/dist/components/link/Link.d.ts +2 -6
  19. package/dist/components/radio/RadioButton.d.ts +5 -0
  20. package/dist/components/searchinput/SearchInput.d.ts +4 -0
  21. package/dist/components/select/Select.d.ts +3 -0
  22. package/dist/components/strings/PktStringsProvider.d.ts +21 -0
  23. package/dist/components/strings/strings-override.test.d.ts +1 -0
  24. package/dist/components/tag/Tag.d.ts +3 -0
  25. package/dist/components/textarea/Textarea.d.ts +3 -0
  26. package/dist/components/textinput/Textinput.d.ts +3 -0
  27. package/dist/components/timepicker/types.d.ts +3 -2
  28. package/dist/components/timepicker/useTimepickerState.d.ts +9 -1
  29. package/dist/punkt-react.cjs +1 -1
  30. package/dist/punkt-react.js +2982 -2757
  31. package/dist/shared-strings/consent.d.ts +71 -0
  32. package/dist/shared-strings/format.d.ts +21 -0
  33. package/dist/shared-strings/index.d.ts +14 -0
  34. package/dist/shared-strings/nb.d.ts +121 -0
  35. package/dist/shared-strings/resolve.d.ts +12 -0
  36. package/dist/shared-strings/store.d.ts +27 -0
  37. package/dist/shared-strings/types.d.ts +40 -0
  38. package/dist/shared-types/accordion.d.ts +58 -0
  39. package/dist/shared-types/alert.d.ts +20 -0
  40. package/dist/shared-types/aria.d.ts +2 -1
  41. package/dist/shared-types/backlink.d.ts +23 -0
  42. package/dist/shared-types/breadcrumbs.d.ts +33 -0
  43. package/dist/shared-types/fileupload.d.ts +55 -28
  44. package/dist/shared-types/footer.d.ts +10 -0
  45. package/dist/shared-types/form-field.d.ts +45 -0
  46. package/dist/shared-types/header-footer.d.ts +2 -0
  47. package/dist/shared-types/index.d.ts +15 -6
  48. package/dist/shared-types/render-link.d.ts +22 -0
  49. package/dist/shared-types/skin.d.ts +2 -9
  50. package/dist/shared-utils/combobox/index.d.ts +1 -0
  51. package/dist/shared-utils/combobox/input-utils.d.ts +2 -1
  52. package/dist/shared-utils/combobox/messages.d.ts +7 -0
  53. package/dist/shared-utils/combobox/option-utils.d.ts +2 -1
  54. package/dist/shared-utils/combobox/selection-manager.d.ts +3 -2
  55. package/package.json +4 -4
  56. package/src/components/accordion/Accordion.tsx +2 -12
  57. package/src/components/accordion/AccordionItem.tsx +2 -8
  58. package/src/components/alert/Alert.tsx +12 -5
  59. package/src/components/backlink/BackLink.tsx +12 -12
  60. package/src/components/breadcrumbs/Breadcrumbs.tsx +19 -15
  61. package/src/components/calendar/Calendar.core.test.tsx +7 -5
  62. package/src/components/calendar/types.ts +14 -34
  63. package/src/components/calendar/useCalendarState.ts +16 -2
  64. package/src/components/checkbox/Checkbox.tsx +20 -4
  65. package/src/components/combobox/Combobox.tsx +1 -0
  66. package/src/components/combobox/Listbox.tsx +4 -2
  67. package/src/components/combobox/types.ts +6 -0
  68. package/src/components/combobox/useComboboxState.ts +15 -8
  69. package/src/components/consent/strings.ts +1 -125
  70. package/src/components/datepicker/DateTags.tsx +4 -3
  71. package/src/components/datepicker/Datepicker.accessibility.test.tsx +2 -2
  72. package/src/components/datepicker/Datepicker.tsx +1 -0
  73. package/src/components/datepicker/DatepickerInputs.tsx +6 -6
  74. package/src/components/datepicker/types.ts +6 -3
  75. package/src/components/datepicker/useDatepickerState.ts +4 -5
  76. package/src/components/fileupload/DropZone.tsx +20 -15
  77. package/src/components/fileupload/FileUpload.test.tsx +37 -0
  78. package/src/components/fileupload/FileUpload.tsx +63 -73
  79. package/src/components/header/HeaderGlobal.tsx +5 -2
  80. package/src/components/header/HeaderService.tsx +12 -7
  81. package/src/components/header/types.ts +6 -0
  82. package/src/components/index.ts +10 -0
  83. package/src/components/inputwrapper/InputWrapper.tsx +28 -6
  84. package/src/components/link/Link.tsx +2 -6
  85. package/src/components/radio/RadioButton.tsx +18 -2
  86. package/src/components/searchinput/SearchInput.tsx +13 -3
  87. package/src/components/select/Select.tsx +5 -0
  88. package/src/components/strings/PktStringsProvider.tsx +38 -0
  89. package/src/components/strings/strings-override.test.tsx +130 -0
  90. package/src/components/tag/Tag.tsx +13 -2
  91. package/src/components/textarea/Textarea.tsx +5 -0
  92. package/src/components/textinput/Textinput.tsx +5 -0
  93. package/src/components/timepicker/Timepicker.tsx +1 -0
  94. package/src/components/timepicker/types.ts +3 -2
  95. package/src/components/timepicker/useTimepickerState.ts +20 -22
@@ -27,7 +27,7 @@ import {
27
27
  useState,
28
28
  } from 'react'
29
29
 
30
- import { defaultFileUploadStrings } from 'shared-types'
30
+ import { type IPktFileUploadBase } from 'shared-types'
31
31
  import {
32
32
  countFileTransferStates,
33
33
  resolveAcceptAttribute,
@@ -52,91 +52,51 @@ import {
52
52
  TItemRenderer,
53
53
  PktFileUploadContext,
54
54
  TQueueItemOperation,
55
- TUploadStrategy,
56
55
  } from './types'
56
+ import { usePktStrings } from '../../hooks/usePktStrings'
57
+ import { formatString, plural } from 'shared-strings'
57
58
 
58
- // Re-export for external use
59
59
  export { formatFileSize, parseFileSize } from 'shared-utils/fileupload'
60
60
 
61
61
  /**
62
62
  * Shared props for `PktFileUpload` (both `form` and `custom` strategies).
63
63
  */
64
- interface IBaseFileUploadProps extends Omit<
65
- InputHTMLAttributes<HTMLInputElement>,
66
- 'checked' | 'size' | 'type' | 'value' | 'onChange' | 'width' | 'defaultValue'
67
- > {
64
+ interface IBaseFileUploadProps
65
+ extends
66
+ Omit<
67
+ IPktFileUploadBase,
68
+ | 'helptext'
69
+ | 'helptextDropdown'
70
+ | 'itemRenderer'
71
+ | 'extraOperations'
72
+ | 'value'
73
+ | 'defaultValue'
74
+ | 'onFilesChanged'
75
+ | 'onFileUploadRequested'
76
+ >,
77
+ Omit<
78
+ InputHTMLAttributes<HTMLInputElement>,
79
+ 'checked' | 'size' | 'type' | 'value' | 'onChange' | 'width' | 'defaultValue' | 'name'
80
+ > {
68
81
  /** Called with the next file list when files are added/removed/updated. */
69
82
  onFilesChanged?: (files: TFileItemList) => void
70
- /** Allow selecting/dropping multiple files. */
71
- multiple?: boolean
72
- /** Controlled value (recommended). */
73
- value?: TFileItemList
74
- /** Upload mode. Defaults to `"form"`. */
75
- uploadStrategy?: TUploadStrategy
76
- /** Field name. Used for native input (`form`) or hidden ID inputs (`custom`). */
77
- name: string
78
- /** Enable comment operation (disabled automatically in thumbnail view). */
79
- addCommentsEnabled?: boolean
80
- /** Enable rename operation (disabled automatically in thumbnail view). */
81
- renameFilesEnabled?: boolean
82
- /** Renderer for queue items (`filename`, `thumbnail`, or custom function). */
83
- itemRenderer?: keyof typeof ItemRenderers | TItemRenderer
84
- /** Number of trailing characters to keep when truncating long filenames. */
85
- truncateTail?: number
86
83
  /**
87
84
  * Called immediately when a file is added in `uploadStrategy="custom"`.
88
85
  * Use it to start uploading the file and update `transfers`.
89
86
  */
90
87
  onFileUploadRequested?: (fileItem: FileItem) => void
88
+ /** Controlled value (recommended). */
89
+ value?: TFileItemList
91
90
  /** Uncontrolled initial value (use either `value` or `defaultValue`, not both). */
92
91
  defaultValue?: TFileItemList
93
- /** Extra operations appended after built-ins (rename/comment). */
94
- extraOperations?: Array<TQueueItemOperation>
95
- /** Stretch to full container width (drop zone + queue). */
96
- fullwidth?: boolean
97
- /** Allowed formats for built-in validation (extensions like `pdf`, or MIME patterns like `image/*`). */
98
- allowedFormats?: string[]
99
- /** Custom message for invalid format. Use `{formats}` placeholder. */
100
- formatErrorMessage?: string
101
- /** Max file size (e.g. `"5MB"` or bytes). */
102
- maxFileSize?: string | number
103
- /** Custom message for size validation. Use `{maxSize}` placeholder. */
104
- sizeErrorMessage?: string
105
- /** Optional additional validation hook (runs after built-in format/size checks). Return string to block. */
106
- onFileValidation?: (file: File) => string | null
107
- /**
108
- * Low-level validation escape hatch — Lit parity with the `file-validate` CustomEvent.
109
- * Receives a detail object whose `errorMessage` can be mutated; set it to reject the file.
110
- */
111
- onFileValidate?: (detail: TFileValidateDetail) => void
112
- /** External/programmatic error message shown under the component. */
113
- errorMessage?: string
114
- /** External error flag (combined with internal validation errors). */
115
- hasError?: boolean
116
- /** Disable the whole component (no interaction). */
117
- disabled?: boolean
118
- /** Optional label/title (wraps the component in `PktInputWrapper`). */
119
- label?: string
92
+ /** Renderer for queue items (`filename`, `thumbnail`, or custom function). */
93
+ itemRenderer?: keyof typeof ItemRenderers | TItemRenderer
120
94
  /** Help text under the label. */
121
95
  helptext?: string | ReactNode
122
96
  /** Expandable help text (dropdown). */
123
97
  helptextDropdown?: string | ReactNode
124
- /** Button label for expandable help text. */
125
- helptextDropdownButton?: string
126
- /** Shows an "optional" tag. */
127
- optionalTag?: boolean
128
- /** Label text for the optional tag. */
129
- optionalText?: string
130
- /** Shows a "required" tag. */
131
- requiredTag?: boolean
132
- /** Label text for the required tag. */
133
- requiredText?: string
134
- /** Extra tag next to the label. */
135
- tagText?: string | null
136
- /** Marks the upload as required. Native validation is used in `form`; submit validation in `custom`. */
137
- required?: boolean
138
- /** Enable image preview modal (only applies to thumbnail renderer). */
139
- enableImagePreview?: boolean
98
+ /** Extra operations appended after built-ins (rename/comment). */
99
+ extraOperations?: Array<TQueueItemOperation>
140
100
  }
141
101
 
142
102
  interface IFileInputFileUploadProps
@@ -193,6 +153,7 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
193
153
  enableImagePreview = false,
194
154
  ['aria-describedby']: ariaDescribedByProp,
195
155
  ['aria-invalid']: ariaInvalidProp,
156
+ strings,
196
157
  ...props
197
158
  }: IPktFileUpload,
198
159
  forwardedRef,
@@ -203,6 +164,7 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
203
164
  useImperativeHandle(forwardedRef, () => fileInputRef.current as HTMLInputElement, [])
204
165
 
205
166
  const transfers = 'transfers' in props ? props.transfers : undefined
167
+ const fileuploadStrings = usePktStrings(['fileupload'], strings).fileupload
206
168
  const [validationError, setValidationError] = useState<string | null>(null)
207
169
  const [addedAnnouncement, setAddedAnnouncement] = useState('')
208
170
 
@@ -262,7 +224,10 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
262
224
  uploadStrategy,
263
225
  required,
264
226
  currentFileCount: value.length,
265
- onMissingFiles: useCallback(() => setValidationError(defaultFileUploadStrings.requiredMissing), []),
227
+ onMissingFiles: useCallback(
228
+ () => setValidationError(fileuploadStrings.requiredMissing),
229
+ [fileuploadStrings],
230
+ ),
266
231
  })
267
232
 
268
233
  const itemRenderer = typeof itemRendererProp === 'string' ? ItemRenderers[itemRendererProp] : itemRendererProp
@@ -293,8 +258,10 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
293
258
  const acceptedItems: TFileItemList = multiple ? addedFileItems : [addedFileItems[0]]
294
259
  setAddedAnnouncement(
295
260
  acceptedItems.length === 1
296
- ? defaultFileUploadStrings.srFileAdded(acceptedItems[0].attributes.targetFilename)
297
- : defaultFileUploadStrings.srFilesAdded(acceptedItems.length),
261
+ ? formatString(fileuploadStrings.srFileAdded, {
262
+ filename: acceptedItems[0].attributes.targetFilename,
263
+ })
264
+ : formatString(fileuploadStrings.srFilesAdded, { count: acceptedItems.length }),
298
265
  )
299
266
 
300
267
  if (!isControlled) setInternalValue(newValue)
@@ -306,7 +273,16 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
306
273
  })
307
274
  }
308
275
  },
309
- [onFilesChanged, value, multiple, uploadStrategy, onFileUploadRequested, validateFile, isControlled],
276
+ [
277
+ onFilesChanged,
278
+ value,
279
+ multiple,
280
+ uploadStrategy,
281
+ onFileUploadRequested,
282
+ validateFile,
283
+ isControlled,
284
+ fileuploadStrings,
285
+ ],
310
286
  )
311
287
 
312
288
  const onFileUpdated = useCallback(
@@ -346,7 +322,11 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
346
322
  [value, transfers],
347
323
  )
348
324
 
349
- const totalLabel = defaultFileUploadStrings.fileLabel(totalCount)
325
+ const totalLabel = plural(
326
+ totalCount,
327
+ fileuploadStrings.fileLabelOne,
328
+ fileuploadStrings.fileLabelOther,
329
+ )
350
330
 
351
331
  const fileUploadContent = (
352
332
  <div
@@ -362,13 +342,21 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
362
342
  <div className="pkt-sr-only" aria-live="polite" aria-atomic="true">
363
343
  {addedAnnouncement ||
364
344
  (totalCount > 0 && uploadedCount > 0
365
- ? defaultFileUploadStrings.srFilesUploadedOfTotal(uploadedCount, totalCount, totalLabel)
345
+ ? formatString(fileuploadStrings.srFilesUploadedOfTotal, {
346
+ uploaded: uploadedCount,
347
+ total: totalCount,
348
+ label: totalLabel,
349
+ })
366
350
  : '')}
367
351
  </div>
368
352
  <div className="pkt-sr-only" aria-live="assertive" aria-atomic="true">
369
353
  {totalCount > 0 &&
370
354
  failedCount > 0 &&
371
- defaultFileUploadStrings.srFilesFailedOfTotal(failedCount, totalCount, totalLabel)}
355
+ formatString(fileuploadStrings.srFilesFailedOfTotal, {
356
+ failed: failedCount,
357
+ total: totalCount,
358
+ label: totalLabel,
359
+ })}
372
360
  </div>
373
361
 
374
362
  <DropZone
@@ -387,6 +375,7 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
387
375
  aria-invalid={isAriaInvalid ? 'true' : undefined}
388
376
  aria-describedby={describedBy}
389
377
  hasVisibleLabel={!!label}
378
+ strings={strings}
390
379
  />
391
380
  {hasError && errorMessage && (
392
381
  <PktAlert
@@ -423,6 +412,7 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
423
412
  return (
424
413
  <div className="pkt-fileupload-component">
425
414
  <PktInputWrapper
415
+ strings={strings}
426
416
  forId={id}
427
417
  label={label}
428
418
  helptext={helptext}
@@ -14,6 +14,7 @@ import { PktHeaderUserMenu } from '../headerUserMenu/UserMenu'
14
14
  import { PktIcon } from '../icon/Icon'
15
15
  import { PktSearchInput } from '../searchinput/SearchInput'
16
16
  import { IPktHeaderGlobal } from './types'
17
+ import { usePktStrings } from '../../hooks/usePktStrings'
17
18
 
18
19
  const defaultLogoPath = 'https://punkt-cdn.oslo.kommune.no/latest/logos/'
19
20
 
@@ -56,6 +57,7 @@ export const PktHeaderGlobal = forwardRef(
56
57
  tabletBreakpoint = 1024,
57
58
  onDataLoaded,
58
59
  onDataError,
60
+ strings,
59
61
  }: IPktHeaderGlobal,
60
62
  ref: ForwardedRef<HTMLElement>,
61
63
  ) => {
@@ -251,10 +253,11 @@ export const PktHeaderGlobal = forwardRef(
251
253
 
252
254
  const showLogoutInUserMenu = logOutButtonPlacement === 'userMenu' || logOutButtonPlacement === 'both'
253
255
 
256
+ const headerStrings = usePktStrings(['header'], strings).header
254
257
  const search = localeData?.search
255
258
  const contact = localeData?.header?.contact
256
- const menuText = localeData?.i18n?.menu || 'Meny'
257
- const searchText = localeData?.i18n?.search || 'Søk'
259
+ const menuText = localeData?.i18n?.menu || headerStrings.menu
260
+ const searchText = localeData?.i18n?.search || headerStrings.search
258
261
  const isMenuOpen = openMenu === 'menu'
259
262
  const isUserOpen = openMenu === 'user'
260
263
  const isSearchOpen = openMenu === 'search'
@@ -26,6 +26,7 @@ import { PktTextinput } from '../textinput/Textinput'
26
26
  import { DEFAULT_HEADER_LOGO_LINK } from 'shared-types'
27
27
 
28
28
  import { IPktHeader, Representing, THeaderMenu, User, UserMenuFooterItem, UserMenuItem } from './types'
29
+ import { usePktStrings } from '../../hooks/usePktStrings'
29
30
 
30
31
  export type { Representing, THeaderMenu, User, UserMenuFooterItem, UserMenuItem }
31
32
  export type IPktHeaderService = IPktHeader
@@ -58,7 +59,7 @@ export const PktHeaderService = forwardRef(
58
59
  onSearch,
59
60
  onSearchChange,
60
61
  searchValue,
61
- searchPlaceholder = 'Søk',
62
+ searchPlaceholder,
62
63
  representing,
63
64
  serviceLink,
64
65
  serviceClick,
@@ -77,6 +78,7 @@ export const PktHeaderService = forwardRef(
77
78
  scrollBehavior = 'hide',
78
79
  slotMenuVariant = 'icon-only',
79
80
  slotMenuText = 'Meny',
81
+ strings,
80
82
  }: IPktHeaderService,
81
83
  ref: ForwardedRef<HTMLDivElement>,
82
84
  ) => {
@@ -92,6 +94,9 @@ export const PktHeaderService = forwardRef(
92
94
  }
93
95
  }, [userMenuFooter])
94
96
 
97
+ const headerStrings = usePktStrings(['header'], strings).header
98
+ const resolvedSearchPlaceholder = searchPlaceholder ?? headerStrings.search
99
+
95
100
  // Warning for userOptions - no longer available
96
101
  useEffect(() => {
97
102
  if (userOptions) {
@@ -341,7 +346,7 @@ export const PktHeaderService = forwardRef(
341
346
  onClick={() => handleMenuToggle('slot')}
342
347
  aria-expanded={openMenu === 'slot'}
343
348
  aria-controls="mobile-slot-menu"
344
- aria-label="Åpne meny"
349
+ aria-label={headerStrings.openMenu}
345
350
  >
346
351
  {slotMenuText}
347
352
  </PktButton>
@@ -375,7 +380,7 @@ export const PktHeaderService = forwardRef(
375
380
  state={openMenu === 'search' ? 'active' : 'normal'}
376
381
  aria-expanded={openMenu === 'search'}
377
382
  aria-controls="mobile-search-menu"
378
- aria-label="Åpne søkefelt"
383
+ aria-label={headerStrings.openSearch}
379
384
  >
380
385
  Søk
381
386
  </PktButton>
@@ -392,9 +397,9 @@ export const PktHeaderService = forwardRef(
392
397
  id="mobile-search-menu"
393
398
  className="pkt-header-service__search-input"
394
399
  type="search"
395
- label="Søk"
400
+ label={headerStrings.search}
396
401
  useWrapper={false}
397
- placeholder={searchPlaceholder}
402
+ placeholder={resolvedSearchPlaceholder}
398
403
  value={searchValue}
399
404
  autoFocus
400
405
  fullwidth={true}
@@ -413,9 +418,9 @@ export const PktHeaderService = forwardRef(
413
418
  id="header-service-search"
414
419
  className="pkt-header-service__search-input"
415
420
  type="search"
416
- label="Søk"
421
+ label={headerStrings.search}
417
422
  useWrapper={false}
418
- placeholder={searchPlaceholder}
423
+ placeholder={resolvedSearchPlaceholder}
419
424
  value={searchValue}
420
425
  onChange={(e: ChangeEvent<HTMLInputElement>) => onSearchChange?.(e.target.value)}
421
426
  onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
@@ -9,6 +9,7 @@ import type {
9
9
  THeaderMenuLocale,
10
10
  IPktHeaderGlobal as ISharedPktHeaderGlobal,
11
11
  } from 'shared-types'
12
+ import type { TPktStringsOverride } from 'shared-strings'
12
13
 
13
14
  export type {
14
15
  THeaderMenu,
@@ -53,6 +54,9 @@ export interface UserMenuFooterItem {
53
54
  }
54
55
 
55
56
  export interface IPktHeader {
57
+ /** Overstyrer tekster for denne komponenten. */
58
+ strings?: TPktStringsOverride
59
+
56
60
  children?: ReactNode
57
61
  /** Additional CSS class name(s) to apply to the header */
58
62
  className?: string
@@ -127,6 +131,8 @@ export interface IPktHeader {
127
131
  * (in shared-types) with React-specific callbacks and DOM props.
128
132
  */
129
133
  export interface IPktHeaderGlobal extends ISharedPktHeaderGlobal {
134
+ /** Overstyrer tekster for denne komponenten. */
135
+ strings?: TPktStringsOverride
130
136
  /** Additional CSS class name(s) to apply to the header */
131
137
  className?: string
132
138
  /** Set dark mode on the header */
@@ -52,3 +52,13 @@ export { PktTextinput } from './textinput/Textinput'
52
52
  export { PktTimepicker } from './timepicker/Timepicker'
53
53
  export * from './interfaces'
54
54
  export * from './types'
55
+ export { PktStringsProvider } from './strings/PktStringsProvider'
56
+ export type { IPktStringsProvider } from './strings/PktStringsProvider'
57
+ export {
58
+ getPktStrings,
59
+ nb as pktStringsNb,
60
+ resetPktStrings,
61
+ setPktStrings,
62
+ subscribePktStrings,
63
+ } from 'shared-strings'
64
+ export type { TPktStrings, TPktStringsNamespace, TPktStringsOverride, IPktStringsProps } from 'shared-strings'
@@ -3,6 +3,8 @@
3
3
  import { ForwardedRef, forwardRef, type ReactNode, RefAttributes } from 'react'
4
4
 
5
5
  import { useFocusModality } from '../../hooks/useFocusModality'
6
+ import { usePktStrings } from '../../hooks/usePktStrings'
7
+ import { formatString, type TPktStringsOverride } from 'shared-strings'
6
8
  import { counterStatusId, isCounterExceeded } from 'shared-utils/forms/described-by'
7
9
  import { PktAlert } from '../alert/Alert'
8
10
  import { PktHelptext } from '../helptext/Helptext'
@@ -12,13 +14,16 @@ export interface IPktInputWrapper extends RefAttributes<HTMLElement> {
12
14
  label: string
13
15
  helptext?: string | ReactNode
14
16
  helptextDropdown?: string | ReactNode
17
+ /** @deprecated Bruk `strings={{ forms: { readMore: '…' } }}` eller `setPktStrings()`. */
15
18
  helptextDropdownButton?: string
16
19
  counter?: boolean
17
20
  counterCurrent?: number
18
21
  counterMaxLength?: number
19
22
  optionalTag?: boolean
23
+ /** @deprecated Bruk `strings={{ forms: { optional: '…' } }}` eller `setPktStrings()`. */
20
24
  optionalText?: string
21
25
  requiredTag?: boolean
26
+ /** @deprecated Bruk `strings={{ forms: { required: '…' } }}` eller `setPktStrings()`. */
22
27
  requiredText?: string
23
28
  tagText?: string | null
24
29
  hasError?: boolean
@@ -33,6 +38,8 @@ export interface IPktInputWrapper extends RefAttributes<HTMLElement> {
33
38
  role?: string
34
39
  counterPosition?: 'top' | 'bottom'
35
40
  size?: 'small' | 'medium' | 'xsmall'
41
+ /** Overstyrer tekster for denne komponenten. */
42
+ strings?: TPktStringsOverride
36
43
  }
37
44
 
38
45
  export const PktInputWrapper = forwardRef(
@@ -47,9 +54,9 @@ export const PktInputWrapper = forwardRef(
47
54
  counterCurrent = 0,
48
55
  counterMaxLength,
49
56
  optionalTag = false,
50
- optionalText = 'Valgfritt',
57
+ optionalText,
51
58
  requiredTag = false,
52
- requiredText = 'Må fylles ut',
59
+ requiredText,
53
60
  tagText = null,
54
61
  hasError = false,
55
62
  errorMessage,
@@ -63,10 +70,12 @@ export const PktInputWrapper = forwardRef(
63
70
  role,
64
71
  counterPosition = 'bottom',
65
72
  size = 'medium',
73
+ strings,
66
74
  }: IPktInputWrapper,
67
75
  ref: ForwardedRef<HTMLDivElement>,
68
76
  ) => {
69
77
  const focusModality = useFocusModality<HTMLDivElement>(ref)
78
+ const s = usePktStrings(['forms', 'validation'], strings)
70
79
 
71
80
  const describedBy = ariaDescribedby || (helptext ? `${forId}-helptext` : undefined)
72
81
  const showCounter = !!counter
@@ -95,7 +104,12 @@ export const PktInputWrapper = forwardRef(
95
104
  {counterMaxLength ? `/${counterMaxLength}` : ''}
96
105
  </div>
97
106
  <div className="pkt-sr-only" id={counterStatusId(forId)} aria-live="polite">
98
- {counterExceeded ? `Du har brukt ${counterCurrent} av ${counterMaxLength} tegn` : ''}
107
+ {counterExceeded
108
+ ? formatString(s.validation.counterExceeded, {
109
+ current: counterCurrent,
110
+ max: counterMaxLength ?? 0,
111
+ })
112
+ : ''}
99
113
  </div>
100
114
  </>
101
115
  ) : null
@@ -104,8 +118,16 @@ export const PktInputWrapper = forwardRef(
104
118
  return (
105
119
  <>
106
120
  {tagText && <span className={tagClasses + ` pkt-tag--gray`}>{tagText}</span>}
107
- {optionalTag && <span className={tagClasses + ` pkt-tag--blue-light`}>{optionalText}</span>}
108
- {requiredTag && <span className={tagClasses + ` pkt-tag--beige`}>{requiredText}</span>}
121
+ {optionalTag && (
122
+ <span className={tagClasses + ` pkt-tag--blue-light`}>
123
+ {optionalText ?? s.forms.optional}
124
+ </span>
125
+ )}
126
+ {requiredTag && (
127
+ <span className={tagClasses + ` pkt-tag--beige`}>
128
+ {requiredText ?? s.forms.required}
129
+ </span>
130
+ )}
109
131
  </>
110
132
  )
111
133
  }
@@ -148,7 +170,7 @@ export const PktInputWrapper = forwardRef(
148
170
  forId={forId}
149
171
  helptext={visibleHelptext}
150
172
  helptextDropdown={helptextDropdown}
151
- helptextDropdownButton={helptextDropdownButton}
173
+ helptextDropdownButton={helptextDropdownButton ?? s.forms.readMore}
152
174
  />
153
175
  )
154
176
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import { AnchorHTMLAttributes, FC, LinkHTMLAttributes, ReactNode } from 'react'
5
+ import type { TRenderLink } from 'shared-types'
5
6
 
6
7
  import { PktIcon } from '../icon/Icon'
7
8
 
@@ -12,12 +13,7 @@ interface IPktLink extends LinkHTMLAttributes<HTMLAnchorElement> {
12
13
  iconPosition?: string | undefined
13
14
  external?: boolean
14
15
  target?: string | undefined
15
- renderLink?: (args: {
16
- href: string
17
- className: string
18
- children: ReactNode
19
- props: AnchorHTMLAttributes<HTMLAnchorElement>
20
- }) => ReactNode
16
+ renderLink?: TRenderLink<ReactNode, AnchorHTMLAttributes<HTMLAnchorElement>>
21
17
  }
22
18
 
23
19
  export const PktLink: FC<IPktLink> = ({
@@ -6,6 +6,8 @@ import {
6
6
  ReactElement,
7
7
  ReactNode,
8
8
  } from 'react'
9
+ import { usePktStrings } from '../../hooks/usePktStrings'
10
+ import type { TPktStringsOverride } from 'shared-strings'
9
11
 
10
12
  export interface IPktRadioButton extends InputHTMLAttributes<HTMLInputElement> {
11
13
  id: string
@@ -19,9 +21,13 @@ export interface IPktRadioButton extends InputHTMLAttributes<HTMLInputElement> {
19
21
  hasError?: boolean
20
22
  value?: string
21
23
  optionalTag?: boolean
24
+ /** @deprecated Bruk `strings={{ forms: { optional: '…' } }}` eller `setPktStrings()`. */
22
25
  optionalText?: string
23
26
  requiredTag?: boolean
27
+ /** @deprecated Bruk `strings={{ forms: { required: '…' } }}` eller `setPktStrings()`. */
24
28
  requiredText?: string
29
+ /** Overstyrer tekster for denne komponenten. */
30
+ strings?: TPktStringsOverride
25
31
  tagText?: string | null
26
32
  onChange?: ChangeEventHandler<HTMLInputElement>
27
33
  }
@@ -43,11 +49,13 @@ export const PktRadioButton = forwardRef(
43
49
  optionalText,
44
50
  requiredTag,
45
51
  requiredText,
52
+ strings,
46
53
  tagText,
47
54
  ...props
48
55
  }: IPktRadioButton,
49
56
  ref: ForwardedRef<HTMLInputElement>,
50
57
  ): ReactElement => {
58
+ const s = usePktStrings(['forms'], strings)
51
59
  const classes = [className, 'pkt-input-check'].filter(Boolean).join(' ')
52
60
 
53
61
  const inputTileClasses = [
@@ -75,8 +83,16 @@ export const PktRadioButton = forwardRef(
75
83
  return (
76
84
  <>
77
85
  {tagText && <span className={tagClasses + ` pkt-tag--gray`}>{tagText}</span>}
78
- {optionalTag && <span className={tagClasses + ` pkt-tag--blue-light`}>{optionalText}</span>}
79
- {requiredTag && <span className={tagClasses + ` pkt-tag--beige`}>{requiredText}</span>}
86
+ {optionalTag && (
87
+ <span className={tagClasses + ` pkt-tag--blue-light`}>
88
+ {optionalText ?? s.forms.optional}
89
+ </span>
90
+ )}
91
+ {requiredTag && (
92
+ <span className={tagClasses + ` pkt-tag--beige`}>
93
+ {requiredText ?? s.forms.required}
94
+ </span>
95
+ )}
80
96
  </>
81
97
  )
82
98
  }
@@ -6,6 +6,8 @@ import type { IPktSearchInputSuggestion } from 'shared-types/searchinput'
6
6
 
7
7
  import { useFocusModality } from '../../hooks/useFocusModality'
8
8
  import { PktButton } from '../button/Button'
9
+ import { usePktStrings } from '../../hooks/usePktStrings'
10
+ import type { TPktStringsOverride } from 'shared-strings'
9
11
 
10
12
  /**
11
13
  * React suggestion item: portable fields from {@link IPktSearchInputSuggestion}
@@ -18,6 +20,9 @@ export type PktSearchInputSuggestion = Omit<IPktSearchInputSuggestion, 'title' |
18
20
  }
19
21
 
20
22
  interface ISearch extends HTMLProps<HTMLInputElement> {
23
+ /** Overstyrer tekster for denne komponenten. */
24
+ strings?: TPktStringsOverride
25
+
21
26
  appearance?: 'local' | 'local-with-button' | 'global'
22
27
  inputSize?: 'small' | 'medium' | 'xsmall'
23
28
  disabled?: boolean
@@ -25,6 +30,7 @@ interface ISearch extends HTMLProps<HTMLInputElement> {
25
30
  id: string
26
31
  label?: string
27
32
  name?: string
33
+ /** @deprecated Bruk `strings={{ searchinput: { placeholder: '…' } }}` eller `setPktStrings()`. */
28
34
  placeholder?: string
29
35
  suggestions?: PktSearchInputSuggestion[]
30
36
  value?: string | undefined
@@ -54,7 +60,7 @@ export const PktSearchInput = forwardRef<HTMLInputElement, ISearchInput | ISearc
54
60
  id,
55
61
  label,
56
62
  name,
57
- placeholder = 'Søk…',
63
+ placeholder,
58
64
  suggestions,
59
65
  value = '',
60
66
  action,
@@ -62,10 +68,14 @@ export const PktSearchInput = forwardRef<HTMLInputElement, ISearchInput | ISearc
62
68
  onChange,
63
69
  onSearch,
64
70
  onSuggestionClick,
71
+ strings,
65
72
  ...props
66
73
  },
67
74
  ref,
68
75
  ) => {
76
+ const s = usePktStrings(['searchinput'], strings)
77
+ const resolvedPlaceholder = placeholder ?? s.searchinput.placeholder
78
+
69
79
  const handleSuggestionActivate = (cb: (() => void) | undefined, index: number) => {
70
80
  if (cb) {
71
81
  cb()
@@ -130,7 +140,7 @@ export const PktSearchInput = forwardRef<HTMLInputElement, ISearchInput | ISearc
130
140
  type="search"
131
141
  name={name || id}
132
142
  id={id}
133
- placeholder={placeholder}
143
+ placeholder={resolvedPlaceholder}
134
144
  defaultValue={value}
135
145
  disabled={disabled}
136
146
  autoComplete="off"
@@ -167,7 +177,7 @@ export const PktSearchInput = forwardRef<HTMLInputElement, ISearchInput | ISearc
167
177
  })
168
178
  }
169
179
  >
170
- {label || placeholder}
180
+ {label || resolvedPlaceholder}
171
181
  </PktButton>
172
182
  </div>
173
183
 
@@ -4,10 +4,13 @@ import { ForwardedRef, forwardRef, ReactNode, SelectHTMLAttributes } from 'react
4
4
 
5
5
  import { PktInputWrapper } from '../inputwrapper/InputWrapper'
6
6
  import { describedByIds } from 'shared-utils/forms/described-by'
7
+ import type { TPktStringsOverride } from 'shared-strings'
7
8
 
8
9
  export type { IPktSelectOption, TSelectOption } from 'shared-types/select'
9
10
 
10
11
  export interface IPktSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
12
+ /** Overstyrer tekster for denne komponenten. */
13
+ strings?: TPktStringsOverride
11
14
  ariaDescribedby?: string
12
15
  ariaLabelledby?: string
13
16
  children?: ReactNode | ReactNode[]
@@ -60,6 +63,7 @@ export const PktSelect = forwardRef(
60
63
  useWrapper = true,
61
64
  hasFieldset,
62
65
  inputSize = 'medium',
66
+ strings,
63
67
  ...props
64
68
  }: IPktSelectProps,
65
69
  ref: ForwardedRef<HTMLSelectElement>,
@@ -69,6 +73,7 @@ export const PktSelect = forwardRef(
69
73
  return (
70
74
  <div className={outerClasses}>
71
75
  <PktInputWrapper
76
+ strings={strings}
72
77
  forId={`${id}-input`}
73
78
  label={label}
74
79
  helptext={helptext}