@oslokommune/punkt-react 16.21.1 → 16.23.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.
@@ -8,6 +8,7 @@ export type { THeaderMenuLocale, IHeaderMenuLink, IHeaderMenuIconLink, IHeaderMe
8
8
  export { DEFAULT_HEADER_FOOTER_URL } from './header-menu';
9
9
  export type { TLayout, TVerticalPosition, THorizontalPosition, TPosition, TPositionWithCenter, } from './layout';
10
10
  export type { THTMLButtonType, TSelectionMode } from './form';
11
+ export type { TSelectOption } from './select';
11
12
  export type { TSize3, TSize5, TSize7 } from './size';
12
13
  export type { TAlertSkin, TMessageboxSkin, TAccordionSkin, TCardSkin, TTagSkin } from './skin';
13
14
  export type { IDateConstraints, ICalendarGridDimensions, IGridCellPosition, TDateRangeMap, ISelectionState, ISelectionOptions, } from './calendar';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shape of a single option in a `<pkt-select>` / `<PktSelect>` component.
3
+ * Lives in shared-types so both `@oslokommune/punkt-elements` and
4
+ * `@oslokommune/punkt-react` can re-export the same canonical type.
5
+ */
6
+ export type TSelectOption = {
7
+ value: string;
8
+ label: string;
9
+ selected?: boolean;
10
+ disabled?: boolean;
11
+ hidden?: boolean;
12
+ };
package/package.json CHANGED
@@ -1,15 +1,11 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-react",
3
- "version": "16.21.1",
3
+ "version": "16.23.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",
7
7
  "main": "dist/punkt-react.umd.js",
8
8
  "module": "dist/punkt-react.es.js",
9
- "nohoist": [
10
- "**/@lit",
11
- "**/@lit/**"
12
- ],
13
9
  "files": [
14
10
  "/dist",
15
11
  "src/components/**/*",
@@ -37,9 +33,6 @@
37
33
  "test:watch": "vitest"
38
34
  },
39
35
  "dependencies": {
40
- "@lit-labs/ssr-dom-shim": "^1.2.1",
41
- "@lit/react": "^1.0.7",
42
- "@oslokommune/punkt-elements": "^16.21.1",
43
36
  "classnames": "^2.5.1",
44
37
  "prettier": "^3.3.3",
45
38
  "react-hook-form": "^7.53.0"
@@ -54,7 +47,6 @@
54
47
  "@testing-library/jest-dom": "^6.5.0",
55
48
  "@testing-library/react": "^16.0.1",
56
49
  "@testing-library/user-event": "^14.5.2",
57
- "@types/jest": "^29.5.14",
58
50
  "@types/jest-axe": "^3.5.9",
59
51
  "@types/node": "^20.12.10",
60
52
  "@types/react": "^18.3.5",
@@ -67,9 +59,7 @@
67
59
  "eslint-plugin-react-hooks": "^7.0.1",
68
60
  "eslint-plugin-simple-import-sort": "^12.1.1",
69
61
  "globals": "^14.0.0",
70
- "jest": "^29.7.0",
71
62
  "jest-axe": "^9.0.0",
72
- "jest-environment-jsdom": "^30.4.1",
73
63
  "react-hooks": "^1.0.1",
74
64
  "sass": "^1.78.0",
75
65
  "typescript": "^6.0.2",
@@ -81,7 +71,7 @@
81
71
  "@oslokommune/punkt-assets": "*",
82
72
  "@oslokommune/punkt-css": "*",
83
73
  "react": ">=18.3.1 <20.0.0",
84
- "react-dom": ">=18.2.22 <20.0.0",
74
+ "react-dom": ">=18.3.1 <20.0.0",
85
75
  "react-router-dom": "^6.26.2"
86
76
  },
87
77
  "engines": {
@@ -109,5 +99,5 @@
109
99
  "url": "https://github.com/oslokommune/punkt/issues"
110
100
  },
111
101
  "license": "MIT",
112
- "gitHead": "15799459d01ea67c7770b8603da84069a717a164"
102
+ "gitHead": "72cbebd6103518933a6086ea0eff6c1bd1fb3eeb"
113
103
  }
@@ -34,7 +34,6 @@ describe('PktBreadcrumbs Component', () => {
34
34
  <PktBreadcrumbs breadcrumbs={breadcrumbs} navigationType="router" />
35
35
  </TestRouter>,
36
36
  )
37
- await window.customElements.whenDefined('pkt-icon')
38
37
 
39
38
  expect(getAllByText('Home')).toBeTruthy()
40
39
  expect(getAllByText('Products')).toBeTruthy()
@@ -43,7 +42,6 @@ describe('PktBreadcrumbs Component', () => {
43
42
 
44
43
  it('renders breadcrumbs correctly with anchor navigation', async () => {
45
44
  const { getAllByText } = render(<PktBreadcrumbs breadcrumbs={breadcrumbs} navigationType="anchor" />)
46
- await window.customElements.whenDefined('pkt-icon')
47
45
 
48
46
  expect(getAllByText('Home')).toBeTruthy()
49
47
  expect(getAllByText('Products')).toBeTruthy()
@@ -56,7 +54,6 @@ describe('PktBreadcrumbs Component', () => {
56
54
  <PktBreadcrumbs breadcrumbs={breadcrumbs} navigationType="router" />
57
55
  </TestRouter>,
58
56
  )
59
- await window.customElements.whenDefined('pkt-icon')
60
57
 
61
58
  expect(getAllByText('Home')).toBeTruthy()
62
59
  expect(getAllByText('Products')).toBeTruthy()
@@ -65,7 +62,6 @@ describe('PktBreadcrumbs Component', () => {
65
62
 
66
63
  it('renders back link correctly on mobile with anchor navigation', async () => {
67
64
  const { getAllByText } = render(<PktBreadcrumbs breadcrumbs={breadcrumbs} navigationType="anchor" />)
68
- await window.customElements.whenDefined('pkt-icon')
69
65
 
70
66
  expect(getAllByText('Home')).toBeTruthy()
71
67
  expect(getAllByText('Products')).toBeTruthy()
@@ -74,7 +70,6 @@ describe('PktBreadcrumbs Component', () => {
74
70
 
75
71
  it('renders with no wcag errors with axe', async () => {
76
72
  const { container } = render(<PktBreadcrumbs breadcrumbs={breadcrumbs} navigationType="anchor" />)
77
- await window.customElements.whenDefined('pkt-icon')
78
73
  const results = await axe(container)
79
74
 
80
75
  expect(results).toHaveNoViolations()
@@ -8,7 +8,10 @@ declare global {
8
8
  }
9
9
  }
10
10
 
11
- window.pktAnimationPath = window.pktAnimationPath || 'https://punkt-cdn.oslo.kommune.no/latest/animations/'
11
+ if (typeof window !== 'undefined') {
12
+ window.pktAnimationPath =
13
+ window.pktAnimationPath || 'https://punkt-cdn.oslo.kommune.no/latest/animations/'
14
+ }
12
15
 
13
16
  export interface IPktButton extends ButtonHTMLAttributes<HTMLButtonElement> {
14
17
  iconName?: string
@@ -1,21 +1,13 @@
1
1
  'use client'
2
2
 
3
3
  import classNames from 'classnames'
4
- import {
5
- forwardRef,
6
- HTMLAttributes,
7
- MutableRefObject,
8
- useEffect,
9
- useLayoutEffect,
10
- useRef,
11
- } from 'react'
4
+ import { forwardRef, HTMLAttributes, MutableRefObject, useEffect, useLayoutEffect, useRef } from 'react'
12
5
  import type { Booleanish } from 'shared-types'
13
6
 
14
7
  const defaultPath = 'https://punkt-cdn.oslo.kommune.no/latest/icons/'
15
8
 
16
9
  if (typeof window !== 'undefined') {
17
- window.pktFetch =
18
- window.pktFetch === undefined ? (fetch as unknown as typeof window.pktFetch) : window.pktFetch
10
+ window.pktFetch = window.pktFetch === undefined ? (fetch as unknown as typeof window.pktFetch) : window.pktFetch
19
11
  window.pktIconPath = window.pktIconPath || defaultPath
20
12
  }
21
13
 
@@ -24,8 +16,7 @@ const dlCache: { [key: string]: Promise<string> } = {}
24
16
  const MAX_RETRIES = 2
25
17
  const RETRY_DELAY = 1500
26
18
 
27
- const isSessionStorageAvailable =
28
- typeof Storage !== 'undefined' && typeof sessionStorage !== 'undefined'
19
+ const isSessionStorageAvailable = typeof Storage !== 'undefined' && typeof sessionStorage !== 'undefined'
29
20
 
30
21
  const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect
31
22
 
@@ -45,21 +36,19 @@ const fetchIcon = (url: string): Promise<string> =>
45
36
  const fetchIconWithRetry = async (url: string, retries = MAX_RETRIES): Promise<string> => {
46
37
  try {
47
38
  return await fetchIcon(url)
39
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- we want to catch all errors, including from fetchIcon
48
40
  } catch (error) {
49
41
  if (retries > 0) {
50
42
  await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY))
51
43
  return fetchIconWithRetry(url, retries - 1)
52
44
  }
53
- // eslint-disable-next-line no-console
45
+ // eslint-disable-next-line no-console -- we want to log the error when all retries have been exhausted
54
46
  console.error('Failed to fetch icon: ' + url)
55
47
  return errorSvg
56
48
  }
57
49
  }
58
50
 
59
- const downloadIconOrGetFromCache = async (
60
- name: string,
61
- path: string | undefined,
62
- ): Promise<string | null> => {
51
+ const downloadIconOrGetFromCache = async (name: string, path: string | undefined): Promise<string | null> => {
63
52
  const key = path + name + '.svg'
64
53
 
65
54
  if (isSessionStorageAvailable && sessionStorage.getItem(key)) {
@@ -108,14 +97,12 @@ export const PktIcon = forwardRef<HTMLSpanElement, IPktIcon>(function PktIcon(
108
97
  },
109
98
  forwardedRef,
110
99
  ) {
111
- const resolvedPath =
112
- path ?? (typeof window !== 'undefined' ? window.pktIconPath : defaultPath)
100
+ const resolvedPath = path ?? (typeof window !== 'undefined' ? window.pktIconPath : defaultPath)
113
101
  const elementRef = useRef<HTMLSpanElement | null>(null)
114
102
 
115
103
  const explicitAriaHidden = toBoolean(ariaHidden) ?? toBoolean(ariaHiddenAttr)
116
104
  const explicitAriaLabel = ariaLabel ?? ariaLabelAttr
117
- const effectiveAriaHidden =
118
- explicitAriaHidden !== undefined ? explicitAriaHidden : !explicitAriaLabel
105
+ const effectiveAriaHidden = explicitAriaHidden !== undefined ? explicitAriaHidden : !explicitAriaLabel
119
106
  const effectiveAriaLabel = effectiveAriaHidden ? undefined : explicitAriaLabel || name
120
107
 
121
108
  useIsomorphicLayoutEffect(() => {
@@ -25,26 +25,22 @@ const Wrapper = (props: { onChange: (e: ChangeEvent) => void }) => {
25
25
  describe('PktSearchInput', () => {
26
26
  it('renders with form element when action prop exists', async () => {
27
27
  const { container } = render(<PktSearchInput id="test-search-input" action="/search" />)
28
- await window.customElements.whenDefined('pkt-icon')
29
28
  expect(container.querySelector('form')).toBeInTheDocument()
30
29
  })
31
30
 
32
31
  it('renders with div element when action prop does not exist', async () => {
33
32
  const { container } = render(<PktSearchInput id="test-search-input" />)
34
- await window.customElements.whenDefined('pkt-icon')
35
33
  expect(container.querySelector('div')).toBeInTheDocument()
36
34
  expect(container.querySelector('form')).not.toBeInTheDocument()
37
35
  })
38
36
 
39
37
  it('renders with label element when label prop exists', async () => {
40
38
  const { container } = render(<PktSearchInput id="test-search-input" label="Test Label" />)
41
- await window.customElements.whenDefined('pkt-icon')
42
39
  expect(container.querySelector('label')).toBeInTheDocument()
43
40
  })
44
41
 
45
42
  it('renders with div element when label prop does not exist', async () => {
46
43
  const { container } = render(<PktSearchInput id="test-search-input" />)
47
- await window.customElements.whenDefined('pkt-icon')
48
44
  expect(container.querySelector('div')).toBeInTheDocument()
49
45
  expect(container.querySelector('label')).not.toBeInTheDocument()
50
46
  })
@@ -52,7 +48,6 @@ describe('PktSearchInput', () => {
52
48
  it('calls onSearch prop with input value on button click', async () => {
53
49
  const mockOnSearch = vi.fn()
54
50
  const { getByRole, getByPlaceholderText } = render(<Wrapper onChange={mockOnSearch} />)
55
- await window.customElements.whenDefined('pkt-icon')
56
51
 
57
52
  const inputElement = getByPlaceholderText('Søk…')
58
53
  const buttonElement = getByRole('button')
@@ -76,7 +71,6 @@ describe('PktSearchInput', () => {
76
71
  suggestions={[{ text: 'Ingen resultater', nonInteractive: true }]}
77
72
  />,
78
73
  )
79
- await window.customElements.whenDefined('pkt-icon')
80
74
 
81
75
  const row = screen.getByText('Ingen resultater').closest('.pkt-searchinput__suggestion')
82
76
  expect(row?.tagName.toLowerCase()).toBe('div')
@@ -90,7 +84,6 @@ describe('PktSearchInput', () => {
90
84
  ]
91
85
 
92
86
  render(<PktSearchInput id="test-search-input" suggestions={suggestions} />)
93
- await window.customElements.whenDefined('pkt-icon')
94
87
 
95
88
  // Check if suggestion elements are rendered
96
89
  suggestions.forEach((suggestion) => {
@@ -105,7 +98,6 @@ describe('PktSearchInput', () => {
105
98
  { title: 'Suggestion 2', onClick: vi.fn() },
106
99
  ]
107
100
  const { container } = render(<PktSearchInput id="test-search-input" label="My label" suggestions={suggestions} />)
108
- await window.customElements.whenDefined('pkt-icon')
109
101
  const results = await axe(container)
110
102
  expect(results).toHaveNoViolations()
111
103
  })
@@ -1,15 +1,11 @@
1
1
  'use client'
2
2
 
3
3
  import { ForwardedRef, forwardRef, ReactNode, SelectHTMLAttributes } from 'react'
4
+ import type { TSelectOption } from 'shared-types'
4
5
 
5
6
  import { PktInputWrapper } from '../inputwrapper/InputWrapper'
6
7
 
7
- export type TSelectOption = {
8
- value: string
9
- label: string
10
- selected?: boolean
11
- disabled?: boolean
12
- }
8
+ export type { TSelectOption }
13
9
 
14
10
  export interface IPktSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
15
11
  ariaDescribedby?: string
@@ -14,7 +14,6 @@ const render = async (
14
14
  options?: Omit<RenderOptions, 'queries'> | undefined,
15
15
  ): Promise<RenderResult> => {
16
16
  const renderResult = originalRender(ui, options)
17
- await window.customElements.whenDefined('pkt-icon')
18
17
  return Promise.resolve(renderResult)
19
18
  }
20
19
 
@@ -57,7 +57,6 @@ describe('PktTextarea', () => {
57
57
  describe('accessibility', () => {
58
58
  it('renders with no wcag errors with axe', async () => {
59
59
  const { container } = render(<PktTextarea label="Input Label" id="inputId" />)
60
- await window.customElements.whenDefined('pkt-textarea')
61
60
  const results = await axe(container)
62
61
  expect(results).toHaveNoViolations()
63
62
  })
@@ -269,7 +269,6 @@ describe('PktTimepicker', () => {
269
269
  describe('Accessibility', () => {
270
270
  test('passes axe in default state', async () => {
271
271
  const { container } = render(<PktTimepicker id={id} name="t" label={label} />)
272
- await window.customElements.whenDefined('pkt-icon')
273
272
  const results = await axe(container)
274
273
  expect(results).toHaveNoViolations()
275
274
  })