@oslokommune/punkt-react 16.19.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 +36 -0
- package/dist/index.d.ts +40 -20
- package/dist/punkt-react.es.js +2900 -2865
- package/dist/punkt-react.umd.js +146 -146
- package/package.json +3 -3
- package/src/components/helptext/Helptext.test.tsx +98 -0
- package/src/components/helptext/Helptext.tsx +84 -26
- package/src/components/inputwrapper/InputWrapper.tsx +10 -41
- 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
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
4
|
+
import { vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { PktHelptext } from './Helptext'
|
|
7
|
+
|
|
8
|
+
describe('PktHelptext', () => {
|
|
9
|
+
test('renders the container without the has-helptext modifier when empty', () => {
|
|
10
|
+
const { container } = render(<PktHelptext forId="field" />)
|
|
11
|
+
|
|
12
|
+
const root = container.querySelector('.pkt-inputwrapper__helptext-container')
|
|
13
|
+
expect(root).toBeInTheDocument()
|
|
14
|
+
expect(root).not.toHaveClass('pkt-inputwrapper__has-helptext')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('renders helptext string as HTML inside the helptext div', () => {
|
|
18
|
+
const { container } = render(<PktHelptext forId="field" helptext="Hjelp <strong>her</strong>" />)
|
|
19
|
+
|
|
20
|
+
const helptext = container.querySelector(`#field-helptext`)
|
|
21
|
+
expect(helptext).toBeInTheDocument()
|
|
22
|
+
expect(helptext?.querySelector('strong')).toHaveTextContent('her')
|
|
23
|
+
expect(container.querySelector('.pkt-inputwrapper__helptext-container')).toHaveClass(
|
|
24
|
+
'pkt-inputwrapper__has-helptext',
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('renders helptext ReactNode directly', () => {
|
|
29
|
+
render(
|
|
30
|
+
<PktHelptext forId="field" helptext={<em data-testid="rn">Rich</em>} />,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
expect(screen.getByTestId('rn')).toHaveTextContent('Rich')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('renders children inside pkt-contents', () => {
|
|
37
|
+
const { container } = render(
|
|
38
|
+
<PktHelptext forId="field">
|
|
39
|
+
<span data-testid="slot">Slot content</span>
|
|
40
|
+
</PktHelptext>,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const contents = container.querySelector('.pkt-contents')
|
|
44
|
+
expect(contents).toBeInTheDocument()
|
|
45
|
+
expect(contents?.querySelector('[data-testid="slot"]')).toHaveTextContent('Slot content')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('renders the dropdown toggle button with the default label when no button text is provided', () => {
|
|
49
|
+
render(<PktHelptext forId="field" helptextDropdown="Mer info" />)
|
|
50
|
+
|
|
51
|
+
const button = screen.getByRole('button')
|
|
52
|
+
expect(button.querySelector('.pkt-btn__text')).toHaveTextContent('Les mer')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('renders a custom helptextDropdownButton as HTML', () => {
|
|
56
|
+
render(
|
|
57
|
+
<PktHelptext
|
|
58
|
+
forId="field"
|
|
59
|
+
helptextDropdown="Mer info"
|
|
60
|
+
helptextDropdownButton="Vis <em>detaljer</em>"
|
|
61
|
+
/>,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const button = screen.getByRole('button')
|
|
65
|
+
expect(button.querySelector('em')).toHaveTextContent('detaljer')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('toggles the dropdown open class and calls onToggleHelpText', () => {
|
|
69
|
+
const onToggle = vi.fn()
|
|
70
|
+
const { container } = render(
|
|
71
|
+
<PktHelptext
|
|
72
|
+
forId="field"
|
|
73
|
+
helptextDropdown="Mer info"
|
|
74
|
+
onToggleHelpText={onToggle}
|
|
75
|
+
/>,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const expandable = container.querySelector('.pkt-inputwrapper__helptext-expandable')
|
|
79
|
+
const innerHelptext = expandable?.querySelector('.pkt-inputwrapper__helptext')
|
|
80
|
+
expect(innerHelptext).toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
|
|
81
|
+
|
|
82
|
+
fireEvent.click(screen.getByRole('button'))
|
|
83
|
+
expect(innerHelptext).toHaveClass('pkt-inputwrapper__helptext-expandable-open')
|
|
84
|
+
expect(onToggle).toHaveBeenLastCalledWith(true)
|
|
85
|
+
|
|
86
|
+
fireEvent.click(screen.getByRole('button'))
|
|
87
|
+
expect(innerHelptext).toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
|
|
88
|
+
expect(onToggle).toHaveBeenLastCalledWith(false)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('does not render an id on the helptext div when forId is missing', () => {
|
|
92
|
+
const { container } = render(<PktHelptext helptext="No id here" />)
|
|
93
|
+
|
|
94
|
+
const helptext = container.querySelector('.pkt-inputwrapper__helptext')
|
|
95
|
+
expect(helptext).toBeInTheDocument()
|
|
96
|
+
expect(helptext).not.toHaveAttribute('id')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
@@ -1,37 +1,95 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import
|
|
3
|
+
import classNames from 'classnames'
|
|
4
|
+
import { forwardRef, HTMLAttributes, ReactNode, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
import { PktIcon } from '../icon/Icon'
|
|
7
|
+
|
|
8
|
+
const defaultDropdownButton = "Les mer <span class='pkt-sr-only'>om feltet</span>"
|
|
7
9
|
|
|
8
10
|
export interface IPktHelptextProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
-
helptext?: string | ReactNode
|
|
10
|
-
helptextDropdown?: string | ReactNode
|
|
11
|
+
helptext?: string | ReactNode
|
|
12
|
+
helptextDropdown?: string | ReactNode
|
|
11
13
|
helptextDropdownButton?: string
|
|
12
14
|
forId?: string
|
|
13
|
-
|
|
14
|
-
onToggleHelpText?: (e: CustomEvent) => void
|
|
15
|
+
onToggleHelpText?: (isOpen: boolean) => void
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
const renderContent = (content: string | ReactNode | undefined): ReactNode => {
|
|
19
|
+
if (content === undefined || content === null || content === '') return null
|
|
20
|
+
if (typeof content === 'string') {
|
|
21
|
+
return <span dangerouslySetInnerHTML={{ __html: content }} />
|
|
22
|
+
}
|
|
23
|
+
return content
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const PktHelptext = forwardRef<HTMLDivElement, IPktHelptextProps>(function PktHelptext(
|
|
27
|
+
{
|
|
28
|
+
helptext,
|
|
29
|
+
helptextDropdown,
|
|
30
|
+
helptextDropdownButton,
|
|
31
|
+
forId,
|
|
32
|
+
onToggleHelpText,
|
|
33
|
+
className,
|
|
34
|
+
children,
|
|
35
|
+
...rest
|
|
34
36
|
},
|
|
35
|
-
|
|
37
|
+
ref,
|
|
38
|
+
) {
|
|
39
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
40
|
+
|
|
41
|
+
const toggle = () => {
|
|
42
|
+
const next = !isOpen
|
|
43
|
+
setIsOpen(next)
|
|
44
|
+
onToggleHelpText?.(next)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasContent = Boolean(helptext || helptextDropdown || children)
|
|
48
|
+
|
|
49
|
+
const containerClasses = classNames(
|
|
50
|
+
'pkt-inputwrapper__helptext-container',
|
|
51
|
+
hasContent && 'pkt-inputwrapper__has-helptext',
|
|
52
|
+
className,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div {...rest} ref={ref} className={containerClasses}>
|
|
57
|
+
<div className="pkt-inputwrapper__helptext" id={forId ? `${forId}-helptext` : undefined}>
|
|
58
|
+
{children && <div className="pkt-contents">{children}</div>}
|
|
59
|
+
{renderContent(helptext)}
|
|
60
|
+
</div>
|
|
61
|
+
{helptextDropdown && (
|
|
62
|
+
<div className="pkt-inputwrapper__helptext-expandable">
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
className="pkt-link pkt-link--icon-right pkt-btn pkt-btn--small pkt-btn--tertiary pkt-btn--icon-right"
|
|
66
|
+
onClick={toggle}
|
|
67
|
+
>
|
|
68
|
+
<PktIcon
|
|
69
|
+
className="pkt-btn__icon"
|
|
70
|
+
name={isOpen ? 'chevron-thin-up' : 'chevron-thin-down'}
|
|
71
|
+
/>
|
|
72
|
+
<span
|
|
73
|
+
className="pkt-btn__text"
|
|
74
|
+
dangerouslySetInnerHTML={{
|
|
75
|
+
__html: helptextDropdownButton ?? defaultDropdownButton,
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
</button>
|
|
79
|
+
<div
|
|
80
|
+
className={classNames(
|
|
81
|
+
'pkt-inputwrapper__helptext',
|
|
82
|
+
isOpen
|
|
83
|
+
? 'pkt-inputwrapper__helptext-expandable-open'
|
|
84
|
+
: 'pkt-inputwrapper__helptext-expandable-closed',
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
{renderContent(helptextDropdown)}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
})
|
|
36
94
|
|
|
37
95
|
PktHelptext.displayName = 'PktHelptext'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { ForwardedRef, forwardRef, type ReactNode, RefAttributes
|
|
3
|
+
import { ForwardedRef, forwardRef, type ReactNode, RefAttributes } from 'react'
|
|
4
4
|
|
|
5
5
|
import { useFocusModality } from '../../hooks/useFocusModality'
|
|
6
6
|
import { PktAlert } from '../alert/Alert'
|
|
7
|
-
import {
|
|
7
|
+
import { PktHelptext } from '../helptext/Helptext'
|
|
8
8
|
|
|
9
9
|
export interface IPktInputWrapper extends RefAttributes<HTMLElement> {
|
|
10
10
|
forId: string
|
|
@@ -63,8 +63,6 @@ export const PktInputWrapper = forwardRef(
|
|
|
63
63
|
}: IPktInputWrapper,
|
|
64
64
|
ref: ForwardedRef<HTMLDivElement>,
|
|
65
65
|
) => {
|
|
66
|
-
const [isHelpTextOpen, setIsHelpTextOpen] = useState(false)
|
|
67
|
-
|
|
68
66
|
const focusModality = useFocusModality<HTMLDivElement>(ref)
|
|
69
67
|
|
|
70
68
|
const describedBy = ariaDescribedby || (helptext ? `${forId}-helptext` : undefined)
|
|
@@ -72,8 +70,6 @@ export const PktInputWrapper = forwardRef(
|
|
|
72
70
|
const counterTop = showCounter && counterPosition === 'top'
|
|
73
71
|
const counterBottom = showCounter && counterPosition === 'bottom'
|
|
74
72
|
|
|
75
|
-
const toggleHelpText = () => setIsHelpTextOpen((prev) => !prev)
|
|
76
|
-
|
|
77
73
|
const wrapperClasses = [
|
|
78
74
|
'pkt-inputwrapper',
|
|
79
75
|
className,
|
|
@@ -134,43 +130,16 @@ export const PktInputWrapper = forwardRef(
|
|
|
134
130
|
}
|
|
135
131
|
|
|
136
132
|
const renderHelptext = () => {
|
|
137
|
-
|
|
133
|
+
const visibleHelptext = useWrapper ? helptext : undefined
|
|
134
|
+
if (!visibleHelptext && !helptextDropdown) return null
|
|
138
135
|
|
|
139
136
|
return (
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
{helptextDropdown && (
|
|
147
|
-
<div className="pkt-inputwrapper__helptext-expandable">
|
|
148
|
-
<PktButton
|
|
149
|
-
skin="tertiary"
|
|
150
|
-
size="small"
|
|
151
|
-
variant="icon-right"
|
|
152
|
-
iconName={isHelpTextOpen ? 'chevron-thin-up' : 'chevron-thin-down'}
|
|
153
|
-
className="pkt-link pkt-link--icon-right"
|
|
154
|
-
onClick={toggleHelpText}
|
|
155
|
-
>
|
|
156
|
-
<span
|
|
157
|
-
dangerouslySetInnerHTML={{
|
|
158
|
-
__html: helptextDropdownButton ?? 'Les mer <span class="pkt-sr-only">om inputfeltet</span>',
|
|
159
|
-
}}
|
|
160
|
-
/>
|
|
161
|
-
</PktButton>
|
|
162
|
-
<div
|
|
163
|
-
className={`pkt-inputwrapper__helptext ${
|
|
164
|
-
isHelpTextOpen
|
|
165
|
-
? 'pkt-inputwrapper__helptext-expandable-open'
|
|
166
|
-
: 'pkt-inputwrapper__helptext-expandable-closed'
|
|
167
|
-
}`}
|
|
168
|
-
>
|
|
169
|
-
{helptextDropdown}
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
)}
|
|
173
|
-
</>
|
|
137
|
+
<PktHelptext
|
|
138
|
+
forId={forId}
|
|
139
|
+
helptext={visibleHelptext}
|
|
140
|
+
helptextDropdown={helptextDropdown}
|
|
141
|
+
helptextDropdownButton={helptextDropdownButton}
|
|
142
|
+
/>
|
|
174
143
|
)
|
|
175
144
|
}
|
|
176
145
|
|
|
@@ -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'
|