@oslokommune/punkt-react 16.20.0 → 16.21.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/index.d.ts +36 -15
- package/dist/punkt-react.es.js +47 -24
- package/dist/punkt-react.umd.js +5 -5
- package/package.json +3 -3
- package/src/components/loader/Loader.test.tsx +54 -17
- package/src/components/loader/Loader.tsx +113 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-react",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.21.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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@lit-labs/ssr-dom-shim": "^1.2.1",
|
|
41
41
|
"@lit/react": "^1.0.7",
|
|
42
|
-
"@oslokommune/punkt-elements": "^16.
|
|
42
|
+
"@oslokommune/punkt-elements": "^16.21.0",
|
|
43
43
|
"classnames": "^2.5.1",
|
|
44
44
|
"prettier": "^3.3.3",
|
|
45
45
|
"react-hook-form": "^7.53.0"
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
110
110
|
},
|
|
111
111
|
"license": "MIT",
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "f7f1779ccee5d510e6d011e7ec3edba89b41c255"
|
|
113
113
|
}
|
|
@@ -1,38 +1,75 @@
|
|
|
1
1
|
import '@testing-library/jest-dom'
|
|
2
2
|
|
|
3
|
-
import { render } from '@testing-library/react'
|
|
3
|
+
import { act, render } from '@testing-library/react'
|
|
4
4
|
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
5
|
+
import { vi } from 'vitest'
|
|
5
6
|
|
|
6
7
|
import { PktLoader } from './Loader'
|
|
7
8
|
expect.extend(toHaveNoViolations)
|
|
8
9
|
|
|
9
10
|
describe('PktLoader', () => {
|
|
10
11
|
describe('basic rendering', () => {
|
|
11
|
-
it('renders correctly without any props',
|
|
12
|
+
it('renders correctly without any props', () => {
|
|
12
13
|
const { container } = render(<PktLoader id="loader1" />)
|
|
13
|
-
await window.customElements.whenDefined('pkt-loader')
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
expect(
|
|
15
|
+
const root = container.querySelector('.pkt-loader')
|
|
16
|
+
expect(root).toBeInTheDocument()
|
|
17
|
+
expect(root).toHaveAttribute('id', 'loader1')
|
|
18
|
+
expect(root).toHaveAttribute('role', 'status')
|
|
19
|
+
expect(root).toHaveAttribute('aria-busy', 'true')
|
|
17
20
|
})
|
|
18
21
|
|
|
19
|
-
it('renders with size and variant',
|
|
20
|
-
const { container } = render(
|
|
21
|
-
|
|
22
|
+
it('renders with size and variant', () => {
|
|
23
|
+
const { container } = render(
|
|
24
|
+
<PktLoader id="loader1" size="small" variant="rainbow" message="Loading.." />,
|
|
25
|
+
)
|
|
22
26
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
expect(
|
|
27
|
-
expect(loaderBox).toHaveTextContent('Loading..')
|
|
27
|
+
const root = container.querySelector('.pkt-loader')
|
|
28
|
+
expect(root).toHaveClass('pkt-loader--small')
|
|
29
|
+
expect(root?.querySelector('.pkt-loader__spinner')).toBeInTheDocument()
|
|
30
|
+
expect(root).toHaveTextContent('Loading..')
|
|
28
31
|
})
|
|
29
32
|
|
|
30
|
-
it('renders inline',
|
|
33
|
+
it('renders inline', () => {
|
|
31
34
|
const { container } = render(<PktLoader size="medium" message="Loading.." inline />)
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
expect(container.querySelector('.pkt-loader--inline')).toBeInTheDocument()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('hides children while loading and shows them when not loading', () => {
|
|
39
|
+
const { container, rerender } = render(
|
|
40
|
+
<PktLoader isLoading>
|
|
41
|
+
<span data-testid="child">Innhold</span>
|
|
42
|
+
</PktLoader>,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const slot = container.querySelector('.pkt-contents')
|
|
46
|
+
expect(slot).toHaveClass('pkt-hide')
|
|
47
|
+
expect(slot?.querySelector('[data-testid="child"]')).toBeInTheDocument()
|
|
48
|
+
|
|
49
|
+
rerender(
|
|
50
|
+
<PktLoader isLoading={false}>
|
|
51
|
+
<span data-testid="child">Innhold</span>
|
|
52
|
+
</PktLoader>,
|
|
53
|
+
)
|
|
54
|
+
expect(container.querySelector('.pkt-contents')).not.toHaveClass('pkt-hide')
|
|
55
|
+
expect(container.querySelector('.pkt-loader__spinner')).not.toBeInTheDocument()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('delay', () => {
|
|
60
|
+
it('defers the spinner until after the delay has elapsed', () => {
|
|
61
|
+
vi.useFakeTimers()
|
|
62
|
+
try {
|
|
63
|
+
const { container } = render(<PktLoader delay={500} />)
|
|
64
|
+
expect(container.querySelector('.pkt-loader__spinner')).not.toBeInTheDocument()
|
|
34
65
|
|
|
35
|
-
|
|
66
|
+
act(() => {
|
|
67
|
+
vi.advanceTimersByTime(500)
|
|
68
|
+
})
|
|
69
|
+
expect(container.querySelector('.pkt-loader__spinner')).toBeInTheDocument()
|
|
70
|
+
} finally {
|
|
71
|
+
vi.useRealTimers()
|
|
72
|
+
}
|
|
36
73
|
})
|
|
37
74
|
})
|
|
38
75
|
|
|
@@ -1,33 +1,123 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
// eslint-disable-next-line no-restricted-syntax -- React is required for createComponent
|
|
6
|
-
import React, { FC, ForwardedRef, forwardRef, type ReactElement } from 'react'
|
|
3
|
+
import classNames from 'classnames'
|
|
4
|
+
import { forwardRef, HTMLAttributes, ReactNode, useEffect, useState } from 'react'
|
|
7
5
|
|
|
8
|
-
import
|
|
6
|
+
import { PktIcon } from '../icon/Icon'
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
declare global {
|
|
9
|
+
interface Window {
|
|
10
|
+
pktAnimationPath?: string
|
|
11
|
+
}
|
|
12
|
+
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
if (typeof window !== 'undefined') {
|
|
15
|
+
window.pktAnimationPath =
|
|
16
|
+
window.pktAnimationPath || 'https://punkt-cdn.oslo.kommune.no/latest/animations/'
|
|
17
|
+
}
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
export type TPktLoaderVariant = 'blue' | 'rainbow' | 'shapes'
|
|
20
|
+
export type TPktLoaderSize = 'small' | 'medium' | 'large'
|
|
21
|
+
|
|
22
|
+
export interface IPktLoader extends HTMLAttributes<HTMLDivElement> {
|
|
23
|
+
/**
|
|
24
|
+
* Tid i millisekunder før loader vises. Nyttig når lastetiden kan være
|
|
25
|
+
* så kort at loader ikke trengs.
|
|
26
|
+
*/
|
|
27
|
+
delay?: number
|
|
28
|
+
/**
|
|
29
|
+
* Vises loader inline eller som blokk.
|
|
30
|
+
*/
|
|
31
|
+
inline?: boolean
|
|
32
|
+
/**
|
|
33
|
+
* Styrer om loader eller barnenoder vises. Når `false` vises barnenoder.
|
|
34
|
+
*/
|
|
35
|
+
isLoading?: boolean
|
|
36
|
+
/**
|
|
37
|
+
* Meldingstekst som vises sammen med loader.
|
|
38
|
+
*/
|
|
39
|
+
message?: string | null
|
|
40
|
+
/**
|
|
41
|
+
* Størrelse på loader. Standard er "medium".
|
|
42
|
+
*/
|
|
43
|
+
size?: TPktLoaderSize
|
|
44
|
+
/**
|
|
45
|
+
* Loader-variant. Standard er "shapes" (Oslo-bølger). Andre er "blue" og "rainbow" (spinner-varianter).
|
|
46
|
+
*/
|
|
47
|
+
variant?: TPktLoaderVariant
|
|
48
|
+
/**
|
|
49
|
+
* Overstyrer stien til loader-animasjoner.
|
|
50
|
+
*/
|
|
51
|
+
loadingAnimationPath?: string
|
|
52
|
+
children?: ReactNode
|
|
53
|
+
}
|
|
22
54
|
|
|
23
|
-
|
|
24
|
-
(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
55
|
+
const variantIcon = (variant: TPktLoaderVariant): string => {
|
|
56
|
+
switch (variant) {
|
|
57
|
+
case 'blue':
|
|
58
|
+
return 'spinner-blue'
|
|
59
|
+
case 'rainbow':
|
|
60
|
+
return 'spinner'
|
|
61
|
+
default:
|
|
62
|
+
return 'loader'
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const PktLoader = forwardRef<HTMLDivElement, IPktLoader>(function PktLoader(
|
|
67
|
+
{
|
|
68
|
+
delay = 0,
|
|
69
|
+
inline = false,
|
|
70
|
+
isLoading = true,
|
|
71
|
+
message = null,
|
|
72
|
+
size = 'medium',
|
|
73
|
+
variant = 'shapes',
|
|
74
|
+
loadingAnimationPath = typeof window !== 'undefined' ? window.pktAnimationPath : undefined,
|
|
75
|
+
className,
|
|
76
|
+
children,
|
|
77
|
+
...rest
|
|
30
78
|
},
|
|
31
|
-
|
|
79
|
+
ref,
|
|
80
|
+
) {
|
|
81
|
+
const [shouldDisplayLoader, setShouldDisplayLoader] = useState(delay === 0)
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (delay > 0) {
|
|
85
|
+
setShouldDisplayLoader(false)
|
|
86
|
+
const handle = setTimeout(() => setShouldDisplayLoader(true), delay)
|
|
87
|
+
return () => clearTimeout(handle)
|
|
88
|
+
}
|
|
89
|
+
setShouldDisplayLoader(true)
|
|
90
|
+
}, [delay])
|
|
91
|
+
|
|
92
|
+
const classes = classNames(
|
|
93
|
+
'pkt-loader',
|
|
94
|
+
inline ? 'pkt-loader--inline' : 'pkt-loader--box',
|
|
95
|
+
`pkt-loader--${size}`,
|
|
96
|
+
className,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div
|
|
101
|
+
{...rest}
|
|
102
|
+
ref={ref}
|
|
103
|
+
role="status"
|
|
104
|
+
aria-live="polite"
|
|
105
|
+
aria-busy={isLoading ? 'true' : 'false'}
|
|
106
|
+
className={classes}
|
|
107
|
+
>
|
|
108
|
+
{isLoading && shouldDisplayLoader && (
|
|
109
|
+
<div className="pkt-loader__spinner">
|
|
110
|
+
<PktIcon
|
|
111
|
+
name={variantIcon(variant)}
|
|
112
|
+
path={loadingAnimationPath}
|
|
113
|
+
className={`pkt-loader__svg pkt-loader__${variant}`}
|
|
114
|
+
/>
|
|
115
|
+
{message && <p>{message}</p>}
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
<div className={classNames('pkt-contents', isLoading && 'pkt-hide')}>{children}</div>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
})
|
|
32
122
|
|
|
33
123
|
PktLoader.displayName = 'PktLoader'
|