@oslokommune/punkt-react 16.21.1 → 16.22.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 +18 -0
- package/dist/punkt-react.es.js +9 -43
- package/dist/punkt-react.umd.js +2 -36
- package/dist/shared-types/index.d.ts +1 -0
- package/dist/shared-types/select.d.ts +12 -0
- package/package.json +2 -9
- package/src/components/breadcrumbs/Breadcrumbs.test.tsx +0 -5
- package/src/components/button/Button.tsx +4 -1
- package/src/components/icon/Icon.tsx +8 -21
- package/src/components/searchinput/SearchInput.test.tsx +0 -8
- package/src/components/select/Select.tsx +2 -6
- package/src/components/tag/Tag.test.tsx +0 -1
- package/src/components/textarea/Textarea.test.tsx +0 -1
- package/src/components/timepicker/Timepicker.test.tsx +0 -1
|
@@ -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.
|
|
3
|
+
"version": "16.22.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"
|
|
@@ -109,5 +102,5 @@
|
|
|
109
102
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
110
103
|
},
|
|
111
104
|
"license": "MIT",
|
|
112
|
-
"gitHead": "
|
|
105
|
+
"gitHead": "23fb504541072d6e50b5f23d6c13208e5c0c03ce"
|
|
113
106
|
}
|
|
@@ -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
|
-
|
|
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
|
})
|