@oslokommune/punkt-react 13.6.15 → 13.7.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 (67) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/index.d.ts +38 -74
  3. package/dist/punkt-react.es.js +5358 -35971
  4. package/dist/punkt-react.umd.js +418 -548
  5. package/package.json +11 -15
  6. package/src/components/accordion/Accordion.test.tsx +1 -1
  7. package/src/components/accordion/Accordion.tsx +1 -1
  8. package/src/components/accordion/AccordionItem.tsx +6 -5
  9. package/src/components/alert/Alert.tsx +2 -1
  10. package/src/components/breadcrumbs/Breadcrumbs.test.tsx +16 -4
  11. package/src/components/breadcrumbs/Breadcrumbs.tsx +3 -2
  12. package/src/components/button/Button.tsx +3 -2
  13. package/src/components/checkbox/Checkbox.tsx +4 -3
  14. package/src/components/datepicker/Datepicker.test.tsx +393 -0
  15. package/src/components/footer/Footer.tsx +6 -5
  16. package/src/components/footerSimple/FooterSimple.tsx +4 -3
  17. package/src/components/icon/Icon.test.tsx +6 -19
  18. package/src/components/index.ts +0 -2
  19. package/src/components/input/Input.tsx +4 -3
  20. package/src/components/radio/RadioButton.tsx +3 -2
  21. package/src/components/searchinput/SearchInput.tsx +3 -3
  22. package/src/components/stepper/Stepper.tsx +6 -6
  23. package/src/components/table/Table.tsx +2 -1
  24. package/src/components/table/TableBody.tsx +2 -1
  25. package/src/components/table/TableData.tsx +2 -1
  26. package/src/components/table/TableDataCell.tsx +2 -1
  27. package/src/components/table/TableHeader.tsx +2 -1
  28. package/src/components/table/TableHeaderCell.tsx +2 -1
  29. package/src/components/table/TableRow.tsx +2 -1
  30. package/src/components/tag/Tag.tsx +2 -1
  31. package/src/components/preview/Preview.tsx +0 -274
  32. package/src/components/preview/PreviewCode.tsx +0 -118
  33. package/src/components/preview/PreviewPropEditor.tsx +0 -266
  34. package/src/components/preview/PreviewSpecs.tsx +0 -125
  35. package/src/components/preview/PreviewWithJson.tsx +0 -519
  36. package/src/components/preview/preview-types.ts +0 -42
  37. package/src/components/preview/preview-utils.ts +0 -226
  38. package/src/components/preview/previewJson/accordion.json +0 -84
  39. package/src/components/preview/previewJson/alert.json +0 -27
  40. package/src/components/preview/previewJson/backlink.json +0 -14
  41. package/src/components/preview/previewJson/breadcrumbs.json +0 -17
  42. package/src/components/preview/previewJson/button.json +0 -28
  43. package/src/components/preview/previewJson/card.json +0 -41
  44. package/src/components/preview/previewJson/checkbox.json +0 -21
  45. package/src/components/preview/previewJson/combobox.json +0 -24
  46. package/src/components/preview/previewJson/consent.json +0 -14
  47. package/src/components/preview/previewJson/datepicker.json +0 -14
  48. package/src/components/preview/previewJson/footer-simple.json +0 -17
  49. package/src/components/preview/previewJson/footer.json +0 -29
  50. package/src/components/preview/previewJson/header.json +0 -34
  51. package/src/components/preview/previewJson/icon.json +0 -13
  52. package/src/components/preview/previewJson/input-wrapper.json +0 -39
  53. package/src/components/preview/previewJson/link.json +0 -28
  54. package/src/components/preview/previewJson/linkcard.json +0 -30
  55. package/src/components/preview/previewJson/loader.json +0 -28
  56. package/src/components/preview/previewJson/messagebox.json +0 -28
  57. package/src/components/preview/previewJson/modal.json +0 -65
  58. package/src/components/preview/previewJson/progressbar.json +0 -16
  59. package/src/components/preview/previewJson/radiobutton.json +0 -21
  60. package/src/components/preview/previewJson/searchinput.json +0 -20
  61. package/src/components/preview/previewJson/select.json +0 -73
  62. package/src/components/preview/previewJson/steps.json +0 -72
  63. package/src/components/preview/previewJson/table.json +0 -130
  64. package/src/components/preview/previewJson/tabs.json +0 -33
  65. package/src/components/preview/previewJson/tag.json +0 -26
  66. package/src/components/preview/previewJson/textarea.json +0 -18
  67. package/src/components/preview/previewJson/textinput.json +0 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-react",
3
- "version": "13.6.15",
3
+ "version": "13.7.0",
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",
@@ -33,12 +33,13 @@
33
33
  "lint:fix": "eslint --fix 'src/**/*.{jsx,ts,tsx}'",
34
34
  "format": "prettier --write src//**/*.{ts,tsx,css} --config ./.prettierrc",
35
35
  "preview": "vite preview --outDir dist-app",
36
- "test": "react-scripts test --env=jsdom"
36
+ "test": "vitest run",
37
+ "test:watch": "vitest"
37
38
  },
38
39
  "dependencies": {
39
40
  "@lit-labs/ssr-dom-shim": "^1.2.1",
40
41
  "@lit/react": "^1.0.7",
41
- "@oslokommune/punkt-elements": "^13.6.15",
42
+ "@oslokommune/punkt-elements": "^13.7.0",
42
43
  "prettier": "^3.3.3",
43
44
  "react-element-to-jsx-string": "^15.0.0",
44
45
  "react-hook-form": "^7.53.0",
@@ -51,7 +52,7 @@
51
52
  "@testing-library/jest-dom": "^6.5.0",
52
53
  "@testing-library/react": "^16.0.1",
53
54
  "@testing-library/user-event": "^14.5.2",
54
- "@types/jest": "^29.5.12",
55
+ "@types/jest": "^29.5.14",
55
56
  "@types/jest-axe": "^3.5.9",
56
57
  "@types/node": "^20.12.10",
57
58
  "@types/react": "^18.3.5",
@@ -59,7 +60,7 @@
59
60
  "@types/react-syntax-highlighter": "^15.5.13",
60
61
  "@vitejs/plugin-react": "^4.3.1",
61
62
  "classnames": "^2.5.1",
62
- "eslint": "^9.10.0",
63
+ "eslint": "^9.37.0",
63
64
  "eslint-config-prettier": "^9.1.0",
64
65
  "eslint-plugin-prettier": "^5.2.1",
65
66
  "eslint-plugin-simple-import-sort": "^12.1.1",
@@ -67,11 +68,11 @@
67
68
  "jest-axe": "^9.0.0",
68
69
  "jest-environment-jsdom": "^29.7.0",
69
70
  "react-hooks": "^1.0.1",
70
- "react-scripts": "^5.0.1",
71
71
  "sass": "^1.78.0",
72
- "typescript": "^5.6.2",
73
- "vite": "^5.4.18",
74
- "vite-plugin-dts": "^4.2.1"
72
+ "typescript": "^5.9.3",
73
+ "vite": "^6.3.6",
74
+ "vite-plugin-dts": "^4.5.4",
75
+ "vitest": "^3.2.4"
75
76
  },
76
77
  "peerDependencies": {
77
78
  "@oslokommune/punkt-assets": "*",
@@ -97,11 +98,6 @@
97
98
  "ux",
98
99
  "ui"
99
100
  ],
100
- "jest": {
101
- "transformIgnorePatterns": [
102
- "<rootDir>/node_modules/(?!@lit/react)"
103
- ]
104
- },
105
101
  "repository": {
106
102
  "type": "git",
107
103
  "url": "git+https://github.com/oslokommune/punkt.git"
@@ -110,5 +106,5 @@
110
106
  "url": "https://github.com/oslokommune/punkt/issues"
111
107
  },
112
108
  "license": "MIT",
113
- "gitHead": "4798e1ff1529f3e32cd4c4173fab3ec3586319da"
109
+ "gitHead": "cfe27c04046718ff79f60e3babba60062296f05b"
114
110
  }
@@ -3,7 +3,7 @@ import { axe, toHaveNoViolations } from 'jest-axe'
3
3
  import { PktAccordion } from './Accordion'
4
4
  import { PktAccordionItem } from './AccordionItem'
5
5
  import { render, screen, fireEvent } from '@testing-library/react'
6
- import React, { createRef } from 'react'
6
+ import { createRef } from 'react'
7
7
 
8
8
  expect.extend(toHaveNoViolations)
9
9
 
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import React, { ReactNode, forwardRef, createContext, useContext } from 'react'
3
+ import { ReactNode, forwardRef, createContext, useContext } from 'react'
4
4
 
5
5
  export type TPktAccordionSkin = 'borderless' | 'outlined' | 'beige' | 'blue'
6
6
 
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import React, { ReactNode, useState, useEffect, forwardRef } from 'react'
3
+ import { ReactNode, useState, useEffect, forwardRef } from 'react'
4
+ import type { MouseEvent, SyntheticEvent } from 'react'
4
5
  import { PktIcon } from '../icon/Icon'
5
6
  import { useAccordionContext } from './Accordion'
6
7
 
@@ -16,8 +17,8 @@ export interface IPktAccordionItem {
16
17
  children?: ReactNode
17
18
  name?: string
18
19
  className?: string
19
- onClick?: (e: React.MouseEvent<HTMLDetailsElement>) => void
20
- onToggle?: (e: React.SyntheticEvent<HTMLDetailsElement>) => void
20
+ onClick?: (e: MouseEvent<HTMLDetailsElement>) => void
21
+ onToggle?: (e: SyntheticEvent<HTMLDetailsElement>) => void
21
22
  }
22
23
 
23
24
  export const PktAccordionItem = forwardRef<HTMLDetailsElement, IPktAccordionItem>(
@@ -52,7 +53,7 @@ export const PktAccordionItem = forwardRef<HTMLDetailsElement, IPktAccordionItem
52
53
  }
53
54
  }, [defaultOpen, controlledIsOpen])
54
55
 
55
- const handleToggle = (e: React.SyntheticEvent<HTMLDetailsElement>) => {
56
+ const handleToggle = (e: SyntheticEvent<HTMLDetailsElement>) => {
56
57
  const detailsElement = e.currentTarget
57
58
  const newOpenState = detailsElement.open
58
59
 
@@ -63,7 +64,7 @@ export const PktAccordionItem = forwardRef<HTMLDetailsElement, IPktAccordionItem
63
64
  onToggle?.(e)
64
65
  }
65
66
 
66
- const handleClick = (e: React.MouseEvent<HTMLDetailsElement>) => {
67
+ const handleClick = (e: MouseEvent<HTMLDetailsElement>) => {
67
68
  // La native toggle skje først, så trigge onClick
68
69
  setTimeout(() => {
69
70
  onClick?.(e)
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import React, { FC, ForwardedRef, forwardRef, ReactElement } from 'react'
3
+ import { FC, ForwardedRef, forwardRef, ReactElement } from 'react'
4
+ import * as React from 'react'
4
5
  import { createComponent, EventName } from '@lit/react'
5
6
  import { PktAlert as PktElAlert } from '@oslokommune/punkt-elements'
6
7
  import type { PktElType, PktElConstructor } from '@/interfaces/IPktElements'
@@ -6,6 +6,18 @@ import { PktBreadcrumbs } from './Breadcrumbs'
6
6
 
7
7
  expect.extend(toHaveNoViolations)
8
8
 
9
+ // Create a Router with future flags to suppress warnings
10
+ const TestRouter = ({ children }: { children: React.ReactNode }) => (
11
+ <Router
12
+ future={{
13
+ v7_startTransition: true,
14
+ v7_relativeSplatPath: true,
15
+ }}
16
+ >
17
+ {children}
18
+ </Router>
19
+ )
20
+
9
21
  describe('PktBreadcrumbs Component', () => {
10
22
  const breadcrumbs = [
11
23
  { text: 'Home', href: '/' },
@@ -15,9 +27,9 @@ describe('PktBreadcrumbs Component', () => {
15
27
 
16
28
  it('renders breadcrumbs correctly with react-router navigation', async () => {
17
29
  const { getAllByText } = render(
18
- <Router>
30
+ <TestRouter>
19
31
  <PktBreadcrumbs breadcrumbs={breadcrumbs} navigationType="router" />
20
- </Router>,
32
+ </TestRouter>,
21
33
  )
22
34
  await window.customElements.whenDefined('pkt-icon')
23
35
 
@@ -37,9 +49,9 @@ describe('PktBreadcrumbs Component', () => {
37
49
 
38
50
  it('renders back link correctly on mobile with router navigation', async () => {
39
51
  const { getAllByText } = render(
40
- <Router>
52
+ <TestRouter>
41
53
  <PktBreadcrumbs breadcrumbs={breadcrumbs} navigationType="router" />
42
- </Router>,
54
+ </TestRouter>,
43
55
  )
44
56
  await window.customElements.whenDefined('pkt-icon')
45
57
 
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import React, { ForwardedRef, forwardRef } from 'react'
3
+ import { ForwardedRef, forwardRef } from 'react'
4
+ import type { AnchorHTMLAttributes } from 'react'
4
5
  import { Link } from 'react-router-dom'
5
6
 
6
7
  import PktIcon from '../icon/Icon'
@@ -10,7 +11,7 @@ export interface IBreadcrumbs {
10
11
  href: string
11
12
  }
12
13
 
13
- export interface IPktBreadcrumbs extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
14
+ export interface IPktBreadcrumbs extends AnchorHTMLAttributes<HTMLAnchorElement> {
14
15
  breadcrumbs: IBreadcrumbs[]
15
16
  navigationType?: 'router' | 'anchor'
16
17
  }
@@ -1,5 +1,6 @@
1
1
  'use client'
2
- import React, { ForwardedRef, forwardRef } from 'react'
2
+ import { ForwardedRef, forwardRef } from 'react'
3
+ import type { ButtonHTMLAttributes } from 'react'
3
4
 
4
5
  import { PktIcon } from '../icon/Icon'
5
6
  declare global {
@@ -10,7 +11,7 @@ declare global {
10
11
 
11
12
  window.pktAnimationPath = window.pktAnimationPath || 'https://punkt-cdn.oslo.kommune.no/latest/animations/'
12
13
 
13
- export interface IPktButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
14
+ export interface IPktButton extends ButtonHTMLAttributes<HTMLButtonElement> {
14
15
  iconName?: string
15
16
  secondIconName?: string
16
17
  mode?: 'light' | 'dark'
@@ -1,6 +1,7 @@
1
- import React, { ChangeEventHandler, ForwardedRef, forwardRef, ReactNode } from 'react'
1
+ import { ChangeEventHandler, ForwardedRef, forwardRef, ReactNode, ReactElement } from 'react'
2
+ import type { InputHTMLAttributes } from 'react'
2
3
 
3
- export interface IPktCheckbox extends React.InputHTMLAttributes<HTMLInputElement> {
4
+ export interface IPktCheckbox extends InputHTMLAttributes<HTMLInputElement> {
4
5
  id: string
5
6
  hasTile?: boolean
6
7
  disabled?: boolean
@@ -44,7 +45,7 @@ export const PktCheckbox = forwardRef(
44
45
  ...props
45
46
  }: IPktCheckbox,
46
47
  ref: ForwardedRef<HTMLInputElement>,
47
- ): React.ReactElement => {
48
+ ): ReactElement => {
48
49
  const classes = [className, 'pkt-input-check'].filter(Boolean).join(' ')
49
50
 
50
51
  const labelClasses = [
@@ -0,0 +1,393 @@
1
+ import '@testing-library/jest-dom'
2
+ import { fireEvent, render, waitFor } from '@testing-library/react'
3
+ import { axe, toHaveNoViolations } from 'jest-axe'
4
+ import { PktDatepicker, IPktDatepicker } from './Datepicker'
5
+
6
+ const datePickerId = 'datepickerId'
7
+ const label = 'Date Picker Label'
8
+
9
+ const waitForDatepicker = async () => {
10
+ await window.customElements.whenDefined('pkt-datepicker')
11
+ }
12
+
13
+ const waitForCustomElementUpdate = async () => {
14
+ return new Promise((resolve) => {
15
+ requestAnimationFrame(resolve)
16
+ })
17
+ }
18
+
19
+ const createDatepickerTest = (props: Partial<IPktDatepicker> = {}) => {
20
+ const defaultProps: IPktDatepicker = {
21
+ label,
22
+ id: datePickerId,
23
+ ...props,
24
+ }
25
+
26
+ return render(<PktDatepicker {...defaultProps} />)
27
+ }
28
+
29
+ describe('PktDatepicker - React Form Element', () => {
30
+ beforeEach(async () => {
31
+ await waitForDatepicker()
32
+ })
33
+
34
+ expect.extend(toHaveNoViolations)
35
+
36
+ describe('Single date selection', () => {
37
+ test('sets and confirms a single date value', async () => {
38
+ const handleChange = jest.fn()
39
+ const handleValueChange = jest.fn()
40
+ const { container } = createDatepickerTest({
41
+ onChange: handleChange,
42
+ onValueChange: handleValueChange,
43
+ })
44
+
45
+ await waitFor(() => {
46
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
47
+ expect(input).toBeInTheDocument()
48
+
49
+ fireEvent.change(input, { target: { value: '2024-06-15' } })
50
+ fireEvent.blur(input)
51
+ })
52
+
53
+ await waitFor(() => {
54
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
55
+ expect(input.value).toBe('2024-06-15')
56
+
57
+ expect(handleChange).toHaveBeenCalledWith(
58
+ expect.objectContaining({
59
+ target: expect.objectContaining({
60
+ value: '2024-06-15',
61
+ }),
62
+ }),
63
+ )
64
+
65
+ expect(handleValueChange).toHaveBeenCalledWith(
66
+ expect.objectContaining({
67
+ detail: '2024-06-15',
68
+ }),
69
+ )
70
+ })
71
+ })
72
+
73
+ test('sets date via calendar interaction', async () => {
74
+ const handleValueChange = jest.fn()
75
+ const { container } = createDatepickerTest({
76
+ onValueChange: handleValueChange,
77
+ })
78
+
79
+ await waitFor(() => {
80
+ const calendarButton = container.querySelector('button[type="button"]')
81
+ expect(calendarButton).toBeInTheDocument()
82
+ fireEvent.click(calendarButton!)
83
+ })
84
+
85
+ await waitFor(() => {
86
+ const calendar = container.querySelector('pkt-calendar')
87
+ expect(calendar).toBeInTheDocument()
88
+ })
89
+
90
+ await waitFor(() => {
91
+ const dateButtons = container.querySelectorAll('pkt-calendar button[data-date]')
92
+ const firstDateButton = Array.from(dateButtons).find(
93
+ (btn) => btn.textContent && btn.textContent.trim() && !btn.hasAttribute('disabled'),
94
+ ) as HTMLButtonElement
95
+
96
+ if (firstDateButton) {
97
+ fireEvent.click(firstDateButton)
98
+ }
99
+ })
100
+
101
+ await waitForCustomElementUpdate()
102
+
103
+ await waitFor(() => {
104
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
105
+ expect(input.value).not.toBe('')
106
+ expect(handleValueChange).toHaveBeenCalled()
107
+ })
108
+ })
109
+
110
+ test('sets date via keyboard input', async () => {
111
+ const handleValueChange = jest.fn()
112
+ const { container } = createDatepickerTest({
113
+ onValueChange: handleValueChange,
114
+ })
115
+
116
+ await waitFor(() => {
117
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
118
+ expect(input).toBeInTheDocument()
119
+
120
+ fireEvent.change(input, { target: { value: '2025-10-15' } })
121
+ })
122
+
123
+ await waitForCustomElementUpdate()
124
+
125
+ await waitFor(() => {
126
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
127
+ expect(input.value).toBe('2025-10-15')
128
+ })
129
+ })
130
+
131
+ test('has no accessibility violations with single date set', async () => {
132
+ const { container } = createDatepickerTest({
133
+ value: '2024-06-15',
134
+ label: 'Select Date',
135
+ helptext: 'Choose a date from the calendar',
136
+ })
137
+
138
+ await waitForCustomElementUpdate()
139
+
140
+ await waitFor(() => {
141
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
142
+ expect(input.value).toBe('2024-06-15')
143
+ })
144
+
145
+ const results = await axe(container)
146
+ expect(results).toHaveNoViolations()
147
+ })
148
+ })
149
+
150
+ describe('Multiple date selection', () => {
151
+ test('sets and confirms multiple date values', async () => {
152
+ const handleValueChange = jest.fn()
153
+ const { container } = createDatepickerTest({
154
+ multiple: true,
155
+ value: ['2024-06-15', '2024-06-20'],
156
+ onValueChange: handleValueChange,
157
+ })
158
+
159
+ await waitFor(() => {
160
+ const datepicker = container.querySelector('pkt-datepicker')
161
+ expect(datepicker).toHaveAttribute('multiple')
162
+ })
163
+
164
+ await waitForCustomElementUpdate()
165
+
166
+ await waitFor(() => {
167
+ const tags = container.querySelectorAll('pkt-tag')
168
+ expect(tags.length).toBe(2)
169
+ })
170
+ })
171
+
172
+ test('dispatches onChange and onValueChange for multiple dates', async () => {
173
+ const handleChange = jest.fn()
174
+ const handleValueChange = jest.fn()
175
+ const dates = ['2024-06-15', '2024-06-20']
176
+
177
+ const { container } = createDatepickerTest({
178
+ multiple: true,
179
+ value: dates,
180
+ onChange: handleChange,
181
+ onValueChange: handleValueChange,
182
+ })
183
+
184
+ await waitForCustomElementUpdate()
185
+
186
+ await waitFor(() => {
187
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
188
+ fireEvent.change(input, { target: { value: '2024-06-25' } })
189
+ })
190
+
191
+ await waitForCustomElementUpdate()
192
+
193
+ await waitFor(() => {
194
+ expect(handleChange).toHaveBeenCalledWith(
195
+ expect.objectContaining({
196
+ target: expect.objectContaining({
197
+ tagName: 'PKT-DATEPICKER',
198
+ value: '2024-06-15,2024-06-20',
199
+ }),
200
+ }),
201
+ )
202
+
203
+ expect(handleValueChange).toHaveBeenCalledWith(
204
+ expect.objectContaining({
205
+ detail: ['2024-06-15', '2024-06-20'],
206
+ }),
207
+ )
208
+ })
209
+ })
210
+
211
+ test('has no accessibility violations with multiple dates set', async () => {
212
+ const { container } = createDatepickerTest({
213
+ multiple: true,
214
+ value: ['2024-06-15', '2024-06-20', '2024-06-25'],
215
+ label: 'Select Multiple Dates',
216
+ helptext: 'Choose multiple dates from the calendar',
217
+ })
218
+
219
+ await waitForCustomElementUpdate()
220
+
221
+ await waitFor(() => {
222
+ const tags = container.querySelectorAll('pkt-tag')
223
+ expect(tags.length).toBe(3)
224
+ })
225
+
226
+ const results = await axe(container)
227
+ expect(results).toHaveNoViolations()
228
+ })
229
+ })
230
+
231
+ describe('Range date selection', () => {
232
+ test('sets and confirms range date values', async () => {
233
+ const handleValueChange = jest.fn()
234
+ const { container } = createDatepickerTest({
235
+ range: true,
236
+ value: ['2024-06-15', '2024-06-25'],
237
+ onValueChange: handleValueChange,
238
+ })
239
+
240
+ await waitFor(() => {
241
+ const datepicker = container.querySelector('pkt-datepicker')
242
+ expect(datepicker).toHaveAttribute('range')
243
+ const rangeInputs = container.querySelectorAll('input[type="date"]')
244
+ expect(rangeInputs.length).toBe(2)
245
+ })
246
+ })
247
+
248
+ test('has no accessibility violations with range dates set', async () => {
249
+ const { container } = createDatepickerTest({
250
+ range: true,
251
+ value: ['2024-06-15', '2024-06-25'],
252
+ label: 'Select Date Range',
253
+ helptext: 'Choose start and end dates for your range',
254
+ })
255
+
256
+ await waitForCustomElementUpdate()
257
+
258
+ await waitFor(() => {
259
+ const datepicker = container.querySelector('pkt-datepicker')
260
+ expect(datepicker).toHaveAttribute('range')
261
+ expect(datepicker).toHaveAttribute('value', '2024-06-15,2024-06-25')
262
+ })
263
+
264
+ const results = await axe(container)
265
+ expect(results).toHaveNoViolations()
266
+ })
267
+ })
268
+
269
+ describe('Form integration', () => {
270
+ test('works as a form element with name attribute', async () => {
271
+ const { container } = createDatepickerTest({
272
+ name: 'dateField',
273
+ value: '2024-06-15',
274
+ })
275
+
276
+ await waitFor(() => {
277
+ const datepicker = container.querySelector('pkt-datepicker')
278
+ expect(datepicker).toHaveAttribute('name', 'dateField')
279
+ expect(datepicker).toHaveAttribute('value', '2024-06-15')
280
+ })
281
+ })
282
+
283
+ test('handles required validation', async () => {
284
+ const { container } = createDatepickerTest({
285
+ required: true,
286
+ name: 'requiredDate',
287
+ })
288
+
289
+ await waitFor(() => {
290
+ const datepicker = container.querySelector('pkt-datepicker')
291
+ expect(datepicker).toHaveAttribute('required')
292
+ expect(datepicker).toHaveAttribute('name', 'requiredDate')
293
+ })
294
+ })
295
+
296
+ test('handles disabled state', async () => {
297
+ const { container } = createDatepickerTest({
298
+ disabled: true,
299
+ })
300
+
301
+ await waitFor(() => {
302
+ const datepicker = container.querySelector('pkt-datepicker')
303
+ expect(datepicker).toHaveAttribute('disabled')
304
+ })
305
+ })
306
+
307
+ test('sets min and max date boundaries', async () => {
308
+ const { container } = createDatepickerTest({
309
+ min: '2024-01-01',
310
+ max: '2024-12-31',
311
+ value: '2024-06-15',
312
+ })
313
+
314
+ await waitFor(() => {
315
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
316
+ expect(input).toBeInTheDocument()
317
+ expect(input).toHaveAttribute('min', '2024-01-01')
318
+ expect(input).toHaveAttribute('max', '2024-12-31')
319
+ })
320
+ })
321
+ })
322
+
323
+ describe('Event handling', () => {
324
+ test('handles input value changes for form behavior', async () => {
325
+ const { container } = createDatepickerTest({})
326
+
327
+ await waitFor(() => {
328
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
329
+
330
+ fireEvent.change(input, { target: { value: '2024-07-20' } })
331
+ })
332
+
333
+ await waitForCustomElementUpdate()
334
+
335
+ await waitFor(() => {
336
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
337
+ expect(input.value).toBe('2024-07-20')
338
+
339
+ const datepicker = container.querySelector('pkt-datepicker')
340
+ expect(datepicker).toBeInTheDocument()
341
+ })
342
+ })
343
+
344
+ test('triggers focus and blur events', async () => {
345
+ const handleFocus = jest.fn()
346
+ const handleBlur = jest.fn()
347
+ const { container } = createDatepickerTest({
348
+ onFocus: handleFocus,
349
+ onBlur: handleBlur,
350
+ })
351
+
352
+ await waitFor(() => {
353
+ const input = container.querySelector('input[type="date"]') as HTMLInputElement
354
+
355
+ fireEvent.focus(input)
356
+ fireEvent.blur(input)
357
+ })
358
+
359
+ await waitFor(() => {
360
+ expect(handleFocus).toHaveBeenCalled()
361
+ expect(handleBlur).toHaveBeenCalled()
362
+ })
363
+ })
364
+ })
365
+
366
+ describe('Accessibility and form behavior', () => {
367
+ test('has proper label association for screen readers', async () => {
368
+ const { container } = createDatepickerTest({
369
+ label: 'Select your birthday',
370
+ })
371
+
372
+ await waitFor(() => {
373
+ const label = container.querySelector('label')
374
+ const input = container.querySelector('input[type="date"]')
375
+
376
+ expect(label).toHaveTextContent('Select your birthday')
377
+ expect(label).toHaveAttribute('for', expect.stringContaining('input'))
378
+ expect(input).toHaveAttribute('id', expect.stringContaining('input'))
379
+ })
380
+ })
381
+
382
+ test('supports help text for user guidance', async () => {
383
+ const { container } = createDatepickerTest({
384
+ helptext: 'Please select a date in the format DD.MM.YYYY',
385
+ })
386
+
387
+ await waitFor(() => {
388
+ const helpText = container.querySelector('[id*="helptext"]')
389
+ expect(helpText).toBeInTheDocument()
390
+ })
391
+ })
392
+ })
393
+ })
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import React from 'react'
3
+ import { FC, Fragment } from 'react'
4
+ import type { HTMLAttributes } from 'react'
4
5
 
5
6
  import { PktIcon } from '../icon/Icon'
6
7
  import { PktConsent } from '../consent/Consent'
@@ -25,7 +26,7 @@ interface SocialLink {
25
26
  openInNewTab?: boolean
26
27
  }
27
28
 
28
- export interface IPktFooter extends React.HTMLAttributes<HTMLDivElement> {
29
+ export interface IPktFooter extends HTMLAttributes<HTMLDivElement> {
29
30
  columnOne: Column
30
31
  columnTwo: Column
31
32
  socialLinks?: SocialLink[]
@@ -42,7 +43,7 @@ export interface IPktFooter extends React.HTMLAttributes<HTMLDivElement> {
42
43
  onToggleConsent?: (e: CustomEvent) => void
43
44
  }
44
45
 
45
- export const PktFooter: React.FC<IPktFooter> = ({
46
+ export const PktFooter: FC<IPktFooter> = ({
46
47
  columnOne,
47
48
  columnTwo,
48
49
  socialLinks,
@@ -169,7 +170,7 @@ export const PktFooter: React.FC<IPktFooter> = ({
169
170
  {socialLinks
170
171
  .filter((link) => link.iconName)
171
172
  .map((link, index) => (
172
- <React.Fragment key={`sociallinks-${index}`}>
173
+ <Fragment key={`sociallinks-${index}`}>
173
174
  <a
174
175
  href={link.href}
175
176
  aria-label={`til ${link.iconName}`}
@@ -179,7 +180,7 @@ export const PktFooter: React.FC<IPktFooter> = ({
179
180
  >
180
181
  <PktIcon className="pkt-footer__social-icon" name={link.iconName} />
181
182
  </a>
182
- </React.Fragment>
183
+ </Fragment>
183
184
  ))}
184
185
  </div>
185
186
  </div>