@oslokommune/punkt-react 16.5.1 → 16.6.1
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 +22 -6
- package/dist/punkt-react.es.js +3138 -3055
- package/dist/punkt-react.umd.js +109 -107
- package/package.json +3 -3
- package/src/components/progressbar/Progressbar.test.tsx +49 -47
- package/src/components/progressbar/Progressbar.tsx +135 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-react",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.6.1",
|
|
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.6.1",
|
|
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": "a56b9180a633f3fa8b16e9b37c56ffc8255f9b4e"
|
|
113
113
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import '@testing-library/jest-dom'
|
|
2
2
|
|
|
3
3
|
import { render, screen } from '@testing-library/react'
|
|
4
|
-
import exp from 'constants'
|
|
5
4
|
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
6
5
|
import { createRef } from 'react'
|
|
7
6
|
|
|
@@ -11,11 +10,10 @@ expect.extend(toHaveNoViolations)
|
|
|
11
10
|
|
|
12
11
|
describe('PktProgressbar', () => {
|
|
13
12
|
describe('rendering', () => {
|
|
14
|
-
test('renders with default props',
|
|
13
|
+
test('renders with default props', () => {
|
|
15
14
|
const { container } = render(<PktProgressbar id="progress1" valueCurrent={50} />)
|
|
16
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
17
15
|
|
|
18
|
-
const progressbar = container.
|
|
16
|
+
const progressbar = container.firstChild as HTMLElement
|
|
19
17
|
|
|
20
18
|
expect(progressbar).toBeInTheDocument()
|
|
21
19
|
expect(progressbar).toHaveAttribute('id', 'progress1')
|
|
@@ -27,119 +25,124 @@ describe('PktProgressbar', () => {
|
|
|
27
25
|
expect(bar).toBeInTheDocument()
|
|
28
26
|
})
|
|
29
27
|
|
|
30
|
-
test('renders with custom skin',
|
|
28
|
+
test('renders with custom skin', () => {
|
|
31
29
|
const { container } = render(<PktProgressbar id="progress2" valueCurrent={50} skin="red" />)
|
|
32
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
33
30
|
|
|
34
|
-
const progressbar = container.
|
|
31
|
+
const progressbar = container.firstChild as HTMLElement
|
|
35
32
|
const bar = progressbar?.querySelector('.pkt-progressbar__bar--red')
|
|
36
33
|
expect(bar).toBeInTheDocument()
|
|
37
34
|
})
|
|
38
35
|
|
|
39
|
-
test('renders title correctly',
|
|
40
|
-
const { container, getByText } = render(
|
|
41
|
-
|
|
36
|
+
test('renders title correctly', () => {
|
|
37
|
+
const { container, getByText } = render(
|
|
38
|
+
<PktProgressbar id="progress3" valueCurrent={50} title="Progress" />,
|
|
39
|
+
)
|
|
42
40
|
|
|
43
|
-
const progressbar = container.
|
|
41
|
+
const progressbar = container.firstChild as HTMLElement
|
|
44
42
|
|
|
45
|
-
// Select by class
|
|
46
43
|
const title = progressbar?.querySelector('.pkt-progressbar__title')
|
|
47
44
|
expect(title).toBeInTheDocument()
|
|
48
45
|
|
|
49
|
-
// Select by text
|
|
50
46
|
const titleText = getByText('Progress')
|
|
51
47
|
expect(titleText).toBeInTheDocument()
|
|
52
48
|
})
|
|
53
49
|
|
|
54
|
-
test('renders title with center position',
|
|
50
|
+
test('renders title with center position', () => {
|
|
55
51
|
const { container } = render(
|
|
56
52
|
<PktProgressbar id="progress4" valueCurrent={50} title="Progress" titlePosition="center" />,
|
|
57
53
|
)
|
|
58
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
59
54
|
const title = container.querySelector('.pkt-progressbar__title-center')
|
|
60
55
|
expect(title).toBeInTheDocument()
|
|
61
56
|
})
|
|
62
57
|
|
|
63
|
-
test('renders status as percentage',
|
|
64
|
-
const { container } = render(
|
|
65
|
-
|
|
58
|
+
test('renders status as percentage', () => {
|
|
59
|
+
const { container } = render(
|
|
60
|
+
<PktProgressbar id="progress5" valueCurrent={50} statusType="percentage" />,
|
|
61
|
+
)
|
|
66
62
|
const status = container.querySelector('.pkt-progressbar__status')
|
|
67
63
|
expect(status).toBeInTheDocument()
|
|
68
64
|
expect(status).toHaveTextContent('50%')
|
|
69
65
|
})
|
|
70
66
|
|
|
71
|
-
test('renders status as fraction',
|
|
67
|
+
test('renders status as fraction', () => {
|
|
72
68
|
const { container } = render(
|
|
73
69
|
<PktProgressbar id="progress6" valueCurrent={50} valueMax={200} statusType="fraction" />,
|
|
74
70
|
)
|
|
75
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
76
71
|
const status = container.querySelector('.pkt-progressbar__status')
|
|
77
72
|
expect(status).toBeInTheDocument()
|
|
78
73
|
expect(status).toHaveTextContent('50 av 200')
|
|
79
74
|
})
|
|
80
75
|
|
|
81
|
-
test('renders status with center placement',
|
|
76
|
+
test('renders status with center placement', () => {
|
|
82
77
|
const { container } = render(
|
|
83
|
-
<PktProgressbar
|
|
78
|
+
<PktProgressbar
|
|
79
|
+
id="progress7"
|
|
80
|
+
valueCurrent={50}
|
|
81
|
+
statusType="percentage"
|
|
82
|
+
statusPlacement="center"
|
|
83
|
+
/>,
|
|
84
84
|
)
|
|
85
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
86
85
|
const status = container.querySelector('.pkt-progressbar__status--center')
|
|
87
86
|
expect(status).toBeInTheDocument()
|
|
88
87
|
})
|
|
89
88
|
|
|
90
|
-
test('renders status with following placement',
|
|
89
|
+
test('renders status with following placement', () => {
|
|
91
90
|
const { container } = render(
|
|
92
|
-
<PktProgressbar
|
|
91
|
+
<PktProgressbar
|
|
92
|
+
id="progress8"
|
|
93
|
+
valueCurrent={50}
|
|
94
|
+
statusType="percentage"
|
|
95
|
+
statusPlacement="following"
|
|
96
|
+
/>,
|
|
93
97
|
)
|
|
94
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
95
98
|
const status = container.querySelector('.pkt-progressbar__status-placement--following')
|
|
96
99
|
expect(status).toBeInTheDocument()
|
|
97
100
|
})
|
|
98
101
|
|
|
99
|
-
test('handles aria-label correctly',
|
|
100
|
-
const { container } = render(
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
test('handles aria-label correctly', () => {
|
|
103
|
+
const { container } = render(
|
|
104
|
+
<PktProgressbar id="progress9" valueCurrent={50} ariaLabel="Progress bar" />,
|
|
105
|
+
)
|
|
106
|
+
const progressbar = container.firstChild as HTMLElement
|
|
103
107
|
expect(progressbar).toHaveAttribute('aria-label', 'Progress bar')
|
|
104
108
|
expect(progressbar).not.toHaveAttribute('aria-labelledby')
|
|
105
109
|
})
|
|
106
110
|
|
|
107
|
-
test('handles role meter correctly',
|
|
111
|
+
test('handles role meter correctly', () => {
|
|
108
112
|
const { container } = render(
|
|
109
113
|
<PktProgressbar id="progress10" role="meter" valueCurrent={50} ariaLabel="Meter bar" />,
|
|
110
114
|
)
|
|
111
|
-
|
|
112
|
-
const progressbar = container.querySelector('pkt-progressbar')
|
|
115
|
+
const progressbar = container.firstChild as HTMLElement
|
|
113
116
|
expect(progressbar).toHaveAttribute('role', 'meter')
|
|
114
117
|
})
|
|
115
118
|
|
|
116
|
-
test('handles id for title and aria-labelledby correctly',
|
|
117
|
-
const { container } = render(
|
|
118
|
-
|
|
119
|
+
test('handles id for title and aria-labelledby correctly', () => {
|
|
120
|
+
const { container } = render(
|
|
121
|
+
<PktProgressbar valueCurrent={50} title="Progress" id="progressId" />,
|
|
122
|
+
)
|
|
119
123
|
|
|
120
|
-
const progressbar = container.
|
|
124
|
+
const progressbar = container.firstChild as HTMLElement
|
|
121
125
|
|
|
122
126
|
expect(progressbar).toBeInTheDocument()
|
|
123
127
|
expect(progressbar).toHaveAttribute('aria-labelledby', 'progressId-title')
|
|
124
128
|
})
|
|
125
129
|
|
|
126
|
-
test('generates id if none is provided',
|
|
130
|
+
test('generates id if none is provided', () => {
|
|
127
131
|
const { container } = render(<PktProgressbar valueCurrent={50} />)
|
|
128
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
129
132
|
|
|
130
|
-
const progressbar = container.
|
|
133
|
+
const progressbar = container.firstChild as HTMLElement
|
|
131
134
|
|
|
132
135
|
const id = progressbar?.getAttribute('id')
|
|
133
136
|
expect(id).toHaveLength(36)
|
|
134
137
|
})
|
|
135
138
|
|
|
136
|
-
test('sets ref correctly',
|
|
137
|
-
const ref = createRef<
|
|
138
|
-
const { unmount } = render(
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
test('sets ref correctly', () => {
|
|
140
|
+
const ref = createRef<HTMLDivElement>()
|
|
141
|
+
const { unmount } = render(
|
|
142
|
+
<PktProgressbar ref={ref} id="progress-ref" valueCurrent={50} title="Ref bar" />,
|
|
143
|
+
)
|
|
141
144
|
|
|
142
|
-
expect(ref.current).toBeInstanceOf(
|
|
145
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement)
|
|
143
146
|
unmount()
|
|
144
147
|
expect(ref.current).toBeNull()
|
|
145
148
|
})
|
|
@@ -152,7 +155,6 @@ describe('PktProgressbar', () => {
|
|
|
152
155
|
const { container } = render(
|
|
153
156
|
<PktProgressbar id="progress-axe" valueCurrent={50} title="Axe test" role="progressbar" />,
|
|
154
157
|
)
|
|
155
|
-
await window.customElements.whenDefined('pkt-progressbar')
|
|
156
158
|
const results = await axe(container)
|
|
157
159
|
expect(results).toHaveNoViolations()
|
|
158
160
|
})
|
|
@@ -1,31 +1,146 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import classNames from 'classnames'
|
|
4
|
+
import { forwardRef, HTMLAttributes, Ref, useEffect, useMemo, useRef, useState } from 'react'
|
|
5
|
+
import type {
|
|
6
|
+
TAriaLive,
|
|
7
|
+
TProgressbarRole,
|
|
8
|
+
TProgressbarSkin,
|
|
9
|
+
TProgressbarStatusPlacement,
|
|
10
|
+
TProgressbarStatusType,
|
|
11
|
+
TProgressbarTitlePosition,
|
|
12
|
+
} from 'shared-types'
|
|
13
|
+
import { uuidish } from 'shared-utils/utils'
|
|
7
14
|
|
|
8
|
-
|
|
15
|
+
export type { TProgressbarRole, TProgressbarSkin, TProgressbarStatusPlacement, TProgressbarStatusType, TProgressbarTitlePosition }
|
|
9
16
|
|
|
10
|
-
|
|
17
|
+
export interface IPktProgressbar extends Omit<HTMLAttributes<HTMLDivElement>, 'role' | 'title'> {
|
|
18
|
+
ariaLabel?: string | null
|
|
19
|
+
ariaLabelledby?: string | null
|
|
20
|
+
ariaLive?: TAriaLive
|
|
21
|
+
ariaValueText?: string | null
|
|
22
|
+
id?: string
|
|
23
|
+
role?: TProgressbarRole
|
|
24
|
+
skin?: TProgressbarSkin
|
|
25
|
+
statusPlacement?: TProgressbarStatusPlacement
|
|
26
|
+
statusType?: TProgressbarStatusType
|
|
27
|
+
title?: string | null
|
|
28
|
+
titlePosition?: TProgressbarTitlePosition
|
|
29
|
+
valueCurrent: number
|
|
30
|
+
valueMax?: number
|
|
31
|
+
valueMin?: number
|
|
32
|
+
ref?: Ref<HTMLDivElement>
|
|
33
|
+
}
|
|
11
34
|
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
export const PktProgressbar = forwardRef<HTMLDivElement, IPktProgressbar>(
|
|
36
|
+
(
|
|
37
|
+
{
|
|
38
|
+
ariaLabel,
|
|
39
|
+
ariaLabelledby,
|
|
40
|
+
ariaLive = 'polite',
|
|
41
|
+
ariaValueText,
|
|
42
|
+
id,
|
|
43
|
+
role = 'progressbar',
|
|
44
|
+
skin = 'dark-blue',
|
|
45
|
+
statusPlacement = 'following',
|
|
46
|
+
statusType = 'none',
|
|
47
|
+
title,
|
|
48
|
+
titlePosition = 'left',
|
|
49
|
+
valueCurrent = 0,
|
|
50
|
+
valueMax = 100,
|
|
51
|
+
valueMin = 0,
|
|
52
|
+
className,
|
|
53
|
+
...props
|
|
54
|
+
},
|
|
55
|
+
ref,
|
|
56
|
+
) => {
|
|
57
|
+
const generatedId = useMemo(() => uuidish(), [])
|
|
58
|
+
const progressbarId = id || generatedId
|
|
14
59
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
60
|
+
const labelRef = useRef<HTMLSpanElement>(null)
|
|
61
|
+
const [labelWidth, setLabelWidth] = useState(0)
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (labelRef.current) {
|
|
65
|
+
setLabelWidth(labelRef.current.getBoundingClientRect().width || 0)
|
|
66
|
+
}
|
|
67
|
+
}, [valueCurrent])
|
|
68
|
+
|
|
69
|
+
const totalSteps = valueMax - valueMin
|
|
70
|
+
const currentPercentage =
|
|
71
|
+
statusType === 'fraction'
|
|
72
|
+
? Math.round((valueCurrent / totalSteps) * 100)
|
|
73
|
+
: Math.round(((valueCurrent - valueMin) / totalSteps) * 100)
|
|
74
|
+
|
|
75
|
+
const formattedTitle = `${valueCurrent} av ${totalSteps}`
|
|
76
|
+
|
|
77
|
+
const computedAriaValueText =
|
|
78
|
+
statusType === 'fraction' && !ariaValueText
|
|
79
|
+
? `${valueCurrent} av ${valueMax - valueMin}`
|
|
80
|
+
: ariaValueText || undefined
|
|
81
|
+
|
|
82
|
+
const computedAriaLabelledby = !ariaLabel
|
|
83
|
+
? ariaLabelledby || `${progressbarId}-title`
|
|
84
|
+
: undefined
|
|
22
85
|
|
|
23
|
-
export const PktProgressbar = forwardRef(
|
|
24
|
-
({ children, ...props }: IPktProgressbar, ref: ForwardedRef<HTMLElement>): ReactElement => {
|
|
25
86
|
return (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
87
|
+
<div
|
|
88
|
+
ref={ref}
|
|
89
|
+
id={progressbarId}
|
|
90
|
+
role={role}
|
|
91
|
+
aria-live={ariaLive}
|
|
92
|
+
aria-atomic="true"
|
|
93
|
+
aria-valuenow={valueCurrent}
|
|
94
|
+
aria-valuemin={valueMin}
|
|
95
|
+
aria-valuemax={valueMax}
|
|
96
|
+
aria-valuetext={computedAriaValueText}
|
|
97
|
+
aria-label={ariaLabel || undefined}
|
|
98
|
+
aria-labelledby={computedAriaLabelledby}
|
|
99
|
+
className={classNames('pkt-progressbar', className)}
|
|
100
|
+
{...props}
|
|
101
|
+
>
|
|
102
|
+
<div
|
|
103
|
+
className="pkt-progressbar__container"
|
|
104
|
+
style={{
|
|
105
|
+
'--pkt-progress-label-width': `${labelWidth}px`,
|
|
106
|
+
'--pkt-progress-width': `${currentPercentage}%`,
|
|
107
|
+
} as React.CSSProperties}
|
|
108
|
+
>
|
|
109
|
+
{title && (
|
|
110
|
+
<p
|
|
111
|
+
id={`${progressbarId}-title`}
|
|
112
|
+
className={classNames('pkt-progressbar__title', {
|
|
113
|
+
'pkt-progressbar__title-center': titlePosition === 'center',
|
|
114
|
+
})}
|
|
115
|
+
>
|
|
116
|
+
{title}
|
|
117
|
+
</p>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
<div className="pkt-progressbar__bar-wrapper">
|
|
121
|
+
<div className={classNames('pkt-progressbar__bar', `pkt-progressbar__bar--${skin}`)} />
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{statusType !== 'none' && (
|
|
125
|
+
<div
|
|
126
|
+
className={classNames('pkt-progressbar__status', {
|
|
127
|
+
'pkt-progressbar__status--center': statusPlacement === 'center',
|
|
128
|
+
})}
|
|
129
|
+
>
|
|
130
|
+
<span
|
|
131
|
+
ref={labelRef}
|
|
132
|
+
className={classNames({
|
|
133
|
+
'pkt-progressbar__status-placement--following': statusPlacement === 'following',
|
|
134
|
+
'pkt-progressbar__status-placement--center': statusPlacement === 'center',
|
|
135
|
+
'pkt-progressbar__status-placement--left': statusPlacement === 'left',
|
|
136
|
+
})}
|
|
137
|
+
>
|
|
138
|
+
{statusType === 'percentage' ? `${currentPercentage}%` : formattedTitle}
|
|
139
|
+
</span>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
29
144
|
)
|
|
30
145
|
},
|
|
31
146
|
)
|