@oslokommune/punkt-react 16.26.3 → 17.0.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 +111 -0
- package/dist/components/accordion/Accordion.d.ts +1 -0
- package/dist/components/alert/Alert.d.ts +9 -0
- package/dist/components/button/Button.d.ts +1 -1
- package/dist/components/combobox/types.d.ts +2 -0
- package/dist/components/datepicker/types.d.ts +3 -0
- package/dist/components/fileupload/FileUpload.d.ts +12 -2
- package/dist/components/footer/Footer.accessibility.test.d.ts +0 -0
- package/dist/components/footer/Footer.d.ts +12 -34
- package/dist/components/footer/Footer.test.d.ts +1 -0
- package/dist/components/footer/FooterGlobal.d.ts +23 -0
- package/dist/components/footerSimple/FooterSimple.d.ts +19 -16
- package/dist/components/footerSimple/FooterSimple.test.d.ts +1 -0
- package/dist/components/header/Header.d.ts +15 -3
- package/dist/components/header/HeaderGlobal.accessibility.test.d.ts +0 -0
- package/dist/components/header/HeaderGlobal.d.ts +2 -0
- package/dist/components/header/HeaderGlobal.test.d.ts +0 -0
- package/dist/components/header/HeaderService.accessibility.test.d.ts +0 -0
- package/dist/components/header/types.d.ts +30 -2
- package/dist/components/header-menu/HeaderMenu.d.ts +2 -4
- package/dist/components/index.d.ts +4 -0
- package/dist/components/inputwrapper/InputWrapper.d.ts +1 -0
- package/dist/components/searchinput/SearchInput.d.ts +1 -0
- package/dist/components/select/Select.d.ts +4 -7
- package/dist/components/textarea/Textarea.d.ts +2 -0
- package/dist/components/textinput/Textinput.d.ts +1 -0
- package/dist/components/timepicker/types.d.ts +3 -0
- package/dist/components/timepicker/useTimepickerState.d.ts +2 -0
- package/dist/punkt-react.cjs +1 -1
- package/dist/punkt-react.js +3191 -2727
- package/dist/shared-types/footer.d.ts +95 -0
- package/dist/shared-types/{header-menu.d.ts → header-footer.d.ts} +51 -4
- package/dist/shared-types/header.d.ts +1 -1
- package/dist/shared-types/index.d.ts +6 -4
- package/dist/shared-types/searchinput.d.ts +2 -0
- package/dist/shared-types/select.d.ts +3 -2
- package/dist/shared-types/size.d.ts +4 -0
- package/dist/shared-types/timepicker.d.ts +0 -6
- package/dist/shared-utils/datepicker-utils.d.ts +5 -4
- package/dist/shared-utils/fileupload/focus-trap.d.ts +4 -10
- package/dist/shared-utils/focus-trap/index.d.ts +22 -0
- package/dist/shared-utils/{header-menu → header-footer}/example-data.d.ts +5 -4
- package/dist/shared-utils/header-footer/external-domain.d.ts +26 -0
- package/dist/shared-utils/{header-menu → header-footer}/fetch-header-footer-data.d.ts +6 -2
- package/dist/shared-utils/{header-menu → header-footer}/index.d.ts +2 -1
- package/dist/shared-utils/{header-menu → header-footer}/select-megamenu.d.ts +1 -1
- package/dist/shared-utils/scroll-lock/index.d.ts +24 -0
- package/package.json +4 -4
- package/src/components/accordion/Accordion.test.tsx +51 -0
- package/src/components/accordion/Accordion.tsx +3 -3
- package/src/components/accordion/AccordionItem.tsx +7 -14
- package/src/components/alert/Alert.test.tsx +45 -9
- package/src/components/alert/Alert.tsx +13 -3
- package/src/components/button/Button.tsx +2 -2
- package/src/components/combobox/Combobox.core.test.tsx +14 -0
- package/src/components/combobox/Combobox.tsx +1 -0
- package/src/components/combobox/ComboboxInput.tsx +2 -0
- package/src/components/combobox/types.ts +2 -0
- package/src/components/combobox/useComboboxState.ts +2 -0
- package/src/components/consent/Consent.test.tsx +8 -14
- package/src/components/consent/Consent.tsx +9 -19
- package/src/components/datepicker/Datepicker.core.test.tsx +11 -0
- package/src/components/datepicker/Datepicker.tsx +4 -1
- package/src/components/datepicker/DatepickerInputs.tsx +16 -1
- package/src/components/datepicker/types.ts +3 -0
- package/src/components/datepicker/useDatepickerState.ts +17 -9
- package/src/components/fileupload/FileUpload.tsx +27 -9
- package/src/components/footer/Footer.accessibility.test.tsx +46 -0
- package/src/components/footer/Footer.test.tsx +192 -86
- package/src/components/footer/Footer.tsx +169 -159
- package/src/components/footer/FooterGlobal.tsx +181 -0
- package/src/components/footerSimple/FooterSimple.test.tsx +49 -46
- package/src/components/footerSimple/FooterSimple.tsx +117 -78
- package/src/components/header/Header.tsx +17 -4
- package/src/components/header/HeaderGlobal.accessibility.test.tsx +86 -0
- package/src/components/header/HeaderGlobal.test.tsx +254 -0
- package/src/components/header/HeaderGlobal.tsx +467 -0
- package/src/components/header/HeaderService.accessibility.test.tsx +70 -0
- package/src/components/header/HeaderService.test.tsx +29 -16
- package/src/components/header/HeaderService.tsx +24 -84
- package/src/components/header/types.ts +35 -0
- package/src/components/header-menu/HeaderMenu.test.tsx +16 -39
- package/src/components/header-menu/HeaderMenu.tsx +62 -70
- package/src/components/index.ts +4 -0
- package/src/components/inputwrapper/InputWrapper.tsx +6 -1
- package/src/components/modal/Modal.tsx +7 -11
- package/src/components/searchinput/SearchInput.test.tsx +10 -0
- package/src/components/searchinput/SearchInput.tsx +19 -7
- package/src/components/select/Select.test.tsx +18 -0
- package/src/components/select/Select.tsx +8 -4
- package/src/components/textarea/Textarea.test.tsx +10 -0
- package/src/components/textarea/Textarea.tsx +5 -1
- package/src/components/textinput/Textinput.test.tsx +28 -0
- package/src/components/textinput/Textinput.tsx +5 -2
- package/src/components/timepicker/Timepicker.test.tsx +10 -0
- package/src/components/timepicker/Timepicker.tsx +8 -7
- package/src/components/timepicker/types.ts +8 -5
- package/src/components/timepicker/useTimepickerState.ts +4 -0
- package/dist/shared-utils/modal-scroll-lock.d.ts +0 -10
- /package/dist/shared-utils/{header-menu → header-footer}/icon-map.d.ts +0 -0
|
@@ -64,20 +64,56 @@ describe('PktAlert', () => {
|
|
|
64
64
|
${''} | ${undefined}
|
|
65
65
|
${null} | ${undefined}
|
|
66
66
|
${(
|
|
67
|
-
|
|
67
|
+
<span>
|
|
68
68
|
Complex <strong>title</strong>
|
|
69
69
|
</span>
|
|
70
|
-
|
|
71
|
-
`('support complex title %$', async ({title, expected}) => {
|
|
72
|
-
const { container } = render(
|
|
73
|
-
|
|
74
|
-
Alert text
|
|
75
|
-
</PktAlert>,
|
|
76
|
-
)
|
|
77
|
-
const titleElement = container.querySelector('.pkt-alert__title');
|
|
70
|
+
)} | ${'<span>Complex <strong>title</strong></span>'}
|
|
71
|
+
`('support complex title %$', async ({ title, expected }) => {
|
|
72
|
+
const { container } = render(<PktAlert title={title}>Alert text</PktAlert>)
|
|
73
|
+
const titleElement = container.querySelector('.pkt-alert__title')
|
|
78
74
|
expect(titleElement?.innerHTML).toEqual(expected)
|
|
79
75
|
})
|
|
80
76
|
|
|
77
|
+
describe('size and compact', () => {
|
|
78
|
+
test('renders with pkt-alert--medium by default', async () => {
|
|
79
|
+
const { container } = render(<PktAlert>Alert text</PktAlert>)
|
|
80
|
+
const alert = container.querySelector('.pkt-alert')
|
|
81
|
+
expect(alert).toHaveClass('pkt-alert--medium')
|
|
82
|
+
expect(alert).not.toHaveClass('pkt-alert--compact')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test.each`
|
|
86
|
+
size | expectedClass
|
|
87
|
+
${'small'} | ${'pkt-alert--small'}
|
|
88
|
+
${'xsmall'} | ${'pkt-alert--xsmall'}
|
|
89
|
+
`('renders with $expectedClass when size is $size', async ({ size, expectedClass }) => {
|
|
90
|
+
const { container } = render(<PktAlert size={size}>Alert text</PktAlert>)
|
|
91
|
+
const alert = container.querySelector('.pkt-alert')
|
|
92
|
+
expect(alert).toHaveClass(expectedClass)
|
|
93
|
+
expect(alert).not.toHaveClass('pkt-alert--medium')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('renders with pkt-alert--small when compact is set without size', async () => {
|
|
97
|
+
const { container } = render(<PktAlert compact>Alert text</PktAlert>)
|
|
98
|
+
const alert = container.querySelector('.pkt-alert')
|
|
99
|
+
expect(alert).toHaveClass('pkt-alert--small')
|
|
100
|
+
expect(alert).not.toHaveClass('pkt-alert--compact')
|
|
101
|
+
expect(alert).not.toHaveClass('pkt-alert--medium')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('explicit size wins over compact', async () => {
|
|
105
|
+
const { container } = render(
|
|
106
|
+
<PktAlert compact size="xsmall">
|
|
107
|
+
Alert text
|
|
108
|
+
</PktAlert>,
|
|
109
|
+
)
|
|
110
|
+
const alert = container.querySelector('.pkt-alert')
|
|
111
|
+
expect(alert).toHaveClass('pkt-alert--xsmall')
|
|
112
|
+
expect(alert).not.toHaveClass('pkt-alert--compact')
|
|
113
|
+
expect(alert).not.toHaveClass('pkt-alert--small')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
81
117
|
describe('accessibility', () => {
|
|
82
118
|
test('renders with no wcag errors with axe', async () => {
|
|
83
119
|
const { container } = render(<PktAlert title="Error" skin="error"></PktAlert>)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames'
|
|
4
4
|
import { FC, HTMLAttributes, ReactElement, ReactNode, useCallback, useState } from 'react'
|
|
5
|
-
import type { TAlertRole, TAlertSkin,TAriaLive } from 'shared-types'
|
|
5
|
+
import type { TAlertRole, TAlertSkin, TAriaLive } from 'shared-types'
|
|
6
6
|
|
|
7
7
|
import { PktButton } from '../button/Button'
|
|
8
8
|
import { PktIcon } from '../icon/Icon'
|
|
@@ -13,8 +13,17 @@ export interface IPktAlert extends Omit<HTMLAttributes<HTMLDivElement>, 'title'>
|
|
|
13
13
|
title?: ReactNode
|
|
14
14
|
date?: string
|
|
15
15
|
ariaLive?: TAriaLive
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Bruk size i stedet. Uten eksplisitt size gir compact størrelsen 'small'.
|
|
18
|
+
*/
|
|
16
19
|
compact?: boolean
|
|
17
20
|
role?: TAlertRole
|
|
21
|
+
/**
|
|
22
|
+
* Størrelse på varslingen.
|
|
23
|
+
*
|
|
24
|
+
* @default medium
|
|
25
|
+
*/
|
|
26
|
+
size?: 'medium' | 'small' | 'xsmall'
|
|
18
27
|
onClose?: (e: CustomEvent) => void
|
|
19
28
|
}
|
|
20
29
|
|
|
@@ -24,6 +33,7 @@ export const PktAlert: FC<IPktAlert> = ({
|
|
|
24
33
|
children,
|
|
25
34
|
closeAlert,
|
|
26
35
|
compact,
|
|
36
|
+
size = compact ? 'small' : 'medium',
|
|
27
37
|
title,
|
|
28
38
|
date,
|
|
29
39
|
ariaLive: ariaLiveCamel,
|
|
@@ -37,7 +47,7 @@ export const PktAlert: FC<IPktAlert> = ({
|
|
|
37
47
|
|
|
38
48
|
const classes = {
|
|
39
49
|
'pkt-alert': true,
|
|
40
|
-
|
|
50
|
+
[`pkt-alert--${size}`]: size,
|
|
41
51
|
[`pkt-alert--${skin}`]: skin,
|
|
42
52
|
'pkt-hide': isClosed,
|
|
43
53
|
}
|
|
@@ -70,7 +80,7 @@ export const PktAlert: FC<IPktAlert> = ({
|
|
|
70
80
|
<PktButton
|
|
71
81
|
tabIndex={0}
|
|
72
82
|
aria-label="close"
|
|
73
|
-
size={
|
|
83
|
+
size={size}
|
|
74
84
|
type="button"
|
|
75
85
|
skin="tertiary"
|
|
76
86
|
iconName="close"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import { type ButtonHTMLAttributes,ForwardedRef, forwardRef } from 'react'
|
|
2
|
+
import { type ButtonHTMLAttributes, ForwardedRef, forwardRef } from 'react'
|
|
3
3
|
|
|
4
4
|
import { PktIcon } from '../icon/Icon'
|
|
5
5
|
declare global {
|
|
@@ -19,7 +19,7 @@ export interface IPktButton extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
19
19
|
iconPath?: string
|
|
20
20
|
secondIconPath?: string
|
|
21
21
|
mode?: 'light' | 'dark'
|
|
22
|
-
size?: 'small' | 'medium' | 'large'
|
|
22
|
+
size?: 'xsmall' | 'small' | 'medium' | 'large'
|
|
23
23
|
fullWidth?: boolean
|
|
24
24
|
fullWidthOnMobile?: boolean
|
|
25
25
|
color?:
|
|
@@ -107,6 +107,20 @@ describe('PktCombobox', () => {
|
|
|
107
107
|
expect(inputDiv).toHaveClass('pkt-combobox__input--fullwidth')
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
+
test('applies input size classes for small and xsmall', () => {
|
|
111
|
+
const { container: smallContainer } = createComboboxTest({ inputSize: 'small' })
|
|
112
|
+
|
|
113
|
+
const smallInput = smallContainer.querySelector('.pkt-combobox__input')
|
|
114
|
+
expect(smallInput).toHaveClass('pkt-combobox__input--small')
|
|
115
|
+
expect(smallInput).not.toHaveClass('pkt-combobox__input--xsmall')
|
|
116
|
+
|
|
117
|
+
const { container: xsmallContainer } = createComboboxTest({ inputSize: 'xsmall' })
|
|
118
|
+
|
|
119
|
+
const xsmallInput = xsmallContainer.querySelector('.pkt-combobox__input')
|
|
120
|
+
expect(xsmallInput).toHaveClass('pkt-combobox__input--xsmall')
|
|
121
|
+
expect(xsmallInput).not.toHaveClass('pkt-combobox__input--small')
|
|
122
|
+
})
|
|
123
|
+
|
|
110
124
|
test('renders with custom className', () => {
|
|
111
125
|
const { container } = createComboboxTest({ className: 'my-custom-class' })
|
|
112
126
|
|
|
@@ -42,6 +42,7 @@ export const PktCombobox = forwardRef<HTMLDivElement, IPktCombobox>((props, ref)
|
|
|
42
42
|
requiredText={state.requiredText}
|
|
43
43
|
tagText={state.tagText}
|
|
44
44
|
useWrapper={state.useWrapper}
|
|
45
|
+
size={props.inputSize ?? 'medium'}
|
|
45
46
|
counter={state.hasCounter}
|
|
46
47
|
counterCurrent={state.values.length}
|
|
47
48
|
counterMaxLength={state.maxlength}
|
|
@@ -37,10 +37,12 @@ export const ComboboxInput = ({ state }: IComboboxInputProps) => {
|
|
|
37
37
|
handlePlaceholderClick,
|
|
38
38
|
handleSelectOnlyKeydown,
|
|
39
39
|
name,
|
|
40
|
+
inputSize,
|
|
40
41
|
} = state
|
|
41
42
|
|
|
42
43
|
const inputClasses = [
|
|
43
44
|
'pkt-combobox__input',
|
|
45
|
+
`pkt-combobox__input--${inputSize}`,
|
|
44
46
|
fullwidth && 'pkt-combobox__input--fullwidth',
|
|
45
47
|
isOpen && 'pkt-combobox__input--open',
|
|
46
48
|
hasError && 'pkt-combobox__input--error',
|
|
@@ -50,6 +50,7 @@ export interface IPktCombobox {
|
|
|
50
50
|
optionalText?: string
|
|
51
51
|
tagText?: string
|
|
52
52
|
useWrapper?: boolean
|
|
53
|
+
inputSize?: 'small' | 'medium' | 'xsmall'
|
|
53
54
|
|
|
54
55
|
// Events
|
|
55
56
|
onChange?: ChangeEventHandler<HTMLInputElement>
|
|
@@ -109,6 +110,7 @@ export interface IComboboxState {
|
|
|
109
110
|
requiredText?: string
|
|
110
111
|
tagText?: string
|
|
111
112
|
useWrapper: boolean
|
|
113
|
+
inputSize: 'small' | 'medium' | 'xsmall'
|
|
112
114
|
name?: string
|
|
113
115
|
className?: string
|
|
114
116
|
|
|
@@ -72,6 +72,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
72
72
|
requiredText,
|
|
73
73
|
tagText,
|
|
74
74
|
useWrapper = true,
|
|
75
|
+
inputSize = 'medium',
|
|
75
76
|
name,
|
|
76
77
|
className,
|
|
77
78
|
onChange,
|
|
@@ -1104,6 +1105,7 @@ export function useComboboxState(props: IPktCombobox, ref: React.ForwardedRef<HT
|
|
|
1104
1105
|
requiredText,
|
|
1105
1106
|
tagText,
|
|
1106
1107
|
useWrapper,
|
|
1108
|
+
inputSize,
|
|
1107
1109
|
name,
|
|
1108
1110
|
className,
|
|
1109
1111
|
|
|
@@ -65,9 +65,7 @@ describe('PktConsent', () => {
|
|
|
65
65
|
test('renders the default button trigger with the bokmål settings label', () => {
|
|
66
66
|
render(<PktConsent />)
|
|
67
67
|
|
|
68
|
-
expect(
|
|
69
|
-
screen.getByRole('button', { name: 'Innstillinger for informasjonskapsler' }),
|
|
70
|
-
).toBeInTheDocument()
|
|
68
|
+
expect(screen.getByRole('button', { name: 'Innstillinger for informasjonskapsler' })).toBeInTheDocument()
|
|
71
69
|
})
|
|
72
70
|
|
|
73
71
|
test('uses the English settings label when i18nLanguage is "en"', () => {
|
|
@@ -91,14 +89,14 @@ describe('PktConsent', () => {
|
|
|
91
89
|
expect(link).toHaveTextContent('Innstillinger')
|
|
92
90
|
})
|
|
93
91
|
|
|
94
|
-
test('renders a footer link
|
|
95
|
-
const { container } = render(
|
|
96
|
-
<PktConsent triggerType="footerlink" triggerText="Innstillinger" />,
|
|
97
|
-
)
|
|
92
|
+
test('renders a plain footer link when triggerType="footerlink"', () => {
|
|
93
|
+
const { container } = render(<PktConsent triggerType="footerlink" triggerText="Innstillinger" />)
|
|
98
94
|
|
|
99
95
|
const link = container.querySelector('a.pkt-footer__link')
|
|
100
96
|
expect(link).toBeInTheDocument()
|
|
101
|
-
expect(link
|
|
97
|
+
expect(link).toHaveAttribute('aria-haspopup', 'dialog')
|
|
98
|
+
// Ny footer-stil: ren understreket lenke uten ikon.
|
|
99
|
+
expect(link?.querySelector('.pkt-icon')).toBeNull()
|
|
102
100
|
})
|
|
103
101
|
|
|
104
102
|
test('renders an icon-only button when triggerType="icon"', () => {
|
|
@@ -199,17 +197,13 @@ describe('PktConsent', () => {
|
|
|
199
197
|
})
|
|
200
198
|
|
|
201
199
|
test('link trigger has no axe violations', async () => {
|
|
202
|
-
const { container } = render(
|
|
203
|
-
<PktConsent triggerType="link" triggerText="Cookie settings" />,
|
|
204
|
-
)
|
|
200
|
+
const { container } = render(<PktConsent triggerType="link" triggerText="Cookie settings" />)
|
|
205
201
|
const results = await axe(container)
|
|
206
202
|
expect(results).toHaveNoViolations()
|
|
207
203
|
})
|
|
208
204
|
|
|
209
205
|
test('footer link trigger has no axe violations', async () => {
|
|
210
|
-
const { container } = render(
|
|
211
|
-
<PktConsent triggerType="footerlink" triggerText="Cookie settings" />,
|
|
212
|
-
)
|
|
206
|
+
const { container } = render(<PktConsent triggerType="footerlink" triggerText="Cookie settings" />)
|
|
213
207
|
const results = await axe(container)
|
|
214
208
|
expect(results).toHaveNoViolations()
|
|
215
209
|
})
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
forwardRef,
|
|
5
|
-
HTMLAttributes,
|
|
6
|
-
MouseEvent as ReactMouseEvent,
|
|
7
|
-
Ref,
|
|
8
|
-
useEffect,
|
|
9
|
-
useRef,
|
|
10
|
-
} from 'react'
|
|
3
|
+
import { forwardRef, HTMLAttributes, MouseEvent as ReactMouseEvent, Ref, useEffect, useRef } from 'react'
|
|
11
4
|
|
|
12
5
|
import { PktButton } from '../button/Button'
|
|
13
|
-
import { PktIcon } from '../icon/Icon'
|
|
14
6
|
import { consentStrings } from './strings'
|
|
15
7
|
|
|
16
8
|
interface ConsentItem {
|
|
@@ -154,11 +146,8 @@ export const PktConsent = forwardRef<HTMLElement, IPktConsent>(function PktConse
|
|
|
154
146
|
|
|
155
147
|
const triggerInitRef = useRef<() => void>(() => {})
|
|
156
148
|
|
|
157
|
-
const language = (i18nLanguage in consentStrings.i18n
|
|
158
|
-
|
|
159
|
-
: 'nb') as keyof typeof consentStrings.i18n
|
|
160
|
-
const resolvedTriggerText =
|
|
161
|
-
triggerText ?? consentStrings.i18n[language].contentPresentation.buttons.settings
|
|
149
|
+
const language = (i18nLanguage in consentStrings.i18n ? i18nLanguage : 'nb') as keyof typeof consentStrings.i18n
|
|
150
|
+
const resolvedTriggerText = triggerText ?? consentStrings.i18n[language].contentPresentation.buttons.settings
|
|
162
151
|
|
|
163
152
|
useEffect(() => {
|
|
164
153
|
let cancelled = false
|
|
@@ -188,9 +177,7 @@ export const PktConsent = forwardRef<HTMLElement, IPktConsent>(function PktConse
|
|
|
188
177
|
}
|
|
189
178
|
|
|
190
179
|
triggerInitRef.current = () => {
|
|
191
|
-
window.document.dispatchEvent(
|
|
192
|
-
new Event('CookieBannerReady', { bubbles: true, cancelable: true }),
|
|
193
|
-
)
|
|
180
|
+
window.document.dispatchEvent(new Event('CookieBannerReady', { bubbles: true, cancelable: true }))
|
|
194
181
|
|
|
195
182
|
const banner = window.cookieBanner
|
|
196
183
|
const events = window.__cookieEvents
|
|
@@ -232,6 +219,7 @@ export const PktConsent = forwardRef<HTMLElement, IPktConsent>(function PktConse
|
|
|
232
219
|
setTimeout(() => window.cookieBanner?.openCookieModal())
|
|
233
220
|
}
|
|
234
221
|
|
|
222
|
+
// aria-haspopup: triggeren åpner consent-dialogen, den navigerer ikke
|
|
235
223
|
if (triggerType === 'link') {
|
|
236
224
|
return (
|
|
237
225
|
<a
|
|
@@ -239,6 +227,7 @@ export const PktConsent = forwardRef<HTMLElement, IPktConsent>(function PktConse
|
|
|
239
227
|
ref={ref as Ref<HTMLAnchorElement>}
|
|
240
228
|
href="#"
|
|
241
229
|
className="pkt-link"
|
|
230
|
+
aria-haspopup="dialog"
|
|
242
231
|
onClick={openModal}
|
|
243
232
|
>
|
|
244
233
|
{resolvedTriggerText}
|
|
@@ -252,9 +241,9 @@ export const PktConsent = forwardRef<HTMLElement, IPktConsent>(function PktConse
|
|
|
252
241
|
ref={ref as Ref<HTMLAnchorElement>}
|
|
253
242
|
href="#"
|
|
254
243
|
className="pkt-footer__link"
|
|
244
|
+
aria-haspopup="dialog"
|
|
255
245
|
onClick={openModal}
|
|
256
246
|
>
|
|
257
|
-
<PktIcon name="chevron-right" className="pkt-footer__link-icon" />
|
|
258
247
|
{resolvedTriggerText}
|
|
259
248
|
</a>
|
|
260
249
|
)
|
|
@@ -266,6 +255,7 @@ export const PktConsent = forwardRef<HTMLElement, IPktConsent>(function PktConse
|
|
|
266
255
|
skin="tertiary"
|
|
267
256
|
variant="icon-only"
|
|
268
257
|
iconName="cookie"
|
|
258
|
+
aria-haspopup="dialog"
|
|
269
259
|
onClick={openModal}
|
|
270
260
|
>
|
|
271
261
|
{resolvedTriggerText}
|
|
@@ -273,7 +263,7 @@ export const PktConsent = forwardRef<HTMLElement, IPktConsent>(function PktConse
|
|
|
273
263
|
)
|
|
274
264
|
}
|
|
275
265
|
return (
|
|
276
|
-
<PktButton ref={ref as Ref<HTMLButtonElement>} onClick={openModal}>
|
|
266
|
+
<PktButton ref={ref as Ref<HTMLButtonElement>} aria-haspopup="dialog" onClick={openModal}>
|
|
277
267
|
{resolvedTriggerText}
|
|
278
268
|
</PktButton>
|
|
279
269
|
)
|
|
@@ -49,6 +49,17 @@ describe('PktDatepicker', () => {
|
|
|
49
49
|
expect(outer).toBeInTheDocument()
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
+
test.each([
|
|
53
|
+
['small', 'small'],
|
|
54
|
+
['xsmall', 'xsmall'],
|
|
55
|
+
] as const)('applies inputSize="%s" classes on root, input and calendar button', (size, sizeClass) => {
|
|
56
|
+
const { container } = createDatepickerTest({ inputSize: size })
|
|
57
|
+
|
|
58
|
+
expect(container.querySelector(`.pkt-datepicker--${sizeClass}`)).toBeTruthy()
|
|
59
|
+
expect(container.querySelector(`.pkt-input--${sizeClass}`)).toBeTruthy()
|
|
60
|
+
expect(container.querySelector(`.pkt-btn--${sizeClass}`)).toBeTruthy()
|
|
61
|
+
})
|
|
62
|
+
|
|
52
63
|
test('renders datepicker inputs container', () => {
|
|
53
64
|
const { container } = createDatepickerTest()
|
|
54
65
|
|
|
@@ -12,7 +12,9 @@ export type { IPktDatepicker } from './types'
|
|
|
12
12
|
export const PktDatepicker = forwardRef<HTMLDivElement, IPktDatepicker>((props, ref) => {
|
|
13
13
|
const state = useDatepickerState(props, ref)
|
|
14
14
|
|
|
15
|
-
const outerClasses = ['pkt-datepicker', state.className]
|
|
15
|
+
const outerClasses = ['pkt-datepicker', `pkt-datepicker--${state.inputSize}`, state.className]
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.join(' ')
|
|
16
18
|
|
|
17
19
|
return (
|
|
18
20
|
<div className={outerClasses} id={state.id || undefined}>
|
|
@@ -34,6 +36,7 @@ export const PktDatepicker = forwardRef<HTMLDivElement, IPktDatepicker>((props,
|
|
|
34
36
|
requiredText={state.requiredText}
|
|
35
37
|
tagText={state.tagText}
|
|
36
38
|
useWrapper={state.useWrapper}
|
|
39
|
+
size={state.inputSize}
|
|
37
40
|
counter={state.hasCounter}
|
|
38
41
|
counterCurrent={state.values.length}
|
|
39
42
|
counterMaxLength={state.maxlength}
|
|
@@ -52,6 +52,11 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
52
52
|
toggleCalendar()
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const handleInputTouchEnd = (e: React.TouchEvent<HTMLInputElement>) => {
|
|
56
|
+
e.preventDefault()
|
|
57
|
+
toggleCalendar()
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
const renderCalendarButton = () => (
|
|
56
61
|
<button
|
|
57
62
|
ref={btnRef}
|
|
@@ -69,7 +74,13 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
69
74
|
if (multiple) {
|
|
70
75
|
return (
|
|
71
76
|
<>
|
|
72
|
-
<DateTags
|
|
77
|
+
<DateTags
|
|
78
|
+
dates={values}
|
|
79
|
+
dateformat={dateformat}
|
|
80
|
+
idBase={id}
|
|
81
|
+
strings={strings}
|
|
82
|
+
onDateRemoved={handleTagRemoved}
|
|
83
|
+
/>
|
|
73
84
|
<div className="pkt-datepicker__inputs">
|
|
74
85
|
<div className="pkt-input__container">
|
|
75
86
|
<input
|
|
@@ -86,6 +97,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
86
97
|
aria-invalid={hasError}
|
|
87
98
|
aria-errormessage={hasError ? `${id}-error` : undefined}
|
|
88
99
|
onClick={handleInputClick}
|
|
100
|
+
onTouchEnd={handleInputTouchEnd}
|
|
89
101
|
onKeyDown={handleMultipleKeydown}
|
|
90
102
|
onFocus={handleFocus}
|
|
91
103
|
onBlur={handleMultipleBlur}
|
|
@@ -121,6 +133,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
121
133
|
aria-invalid={hasError}
|
|
122
134
|
aria-errormessage={hasError ? `${id}-error` : undefined}
|
|
123
135
|
onClick={handleInputClick}
|
|
136
|
+
onTouchEnd={handleInputTouchEnd}
|
|
124
137
|
onKeyDown={handleRangeFromKeydown}
|
|
125
138
|
onFocus={handleFocus}
|
|
126
139
|
onBlur={handleRangeBlur}
|
|
@@ -144,6 +157,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
144
157
|
aria-label={`${label} ${strings.generic?.to ?? 'Til'}`}
|
|
145
158
|
aria-invalid={hasError}
|
|
146
159
|
onClick={handleInputClick}
|
|
160
|
+
onTouchEnd={handleInputTouchEnd}
|
|
147
161
|
onKeyDown={handleRangeToKeydown}
|
|
148
162
|
onFocus={handleFocus}
|
|
149
163
|
onBlur={handleRangeBlur}
|
|
@@ -177,6 +191,7 @@ export const DatepickerInputs = ({ state }: { state: IDatepickerState }) => {
|
|
|
177
191
|
aria-invalid={hasError}
|
|
178
192
|
aria-errormessage={hasError ? `${id}-error` : undefined}
|
|
179
193
|
onClick={handleInputClick}
|
|
194
|
+
onTouchEnd={handleInputTouchEnd}
|
|
180
195
|
onKeyDown={handleSingleKeydown}
|
|
181
196
|
onFocus={handleFocus}
|
|
182
197
|
onBlur={undefined}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChangeEvent, InputHTMLAttributes, ReactNode } from 'react'
|
|
2
2
|
import type { IDatepickerStrings } from 'shared-types/datepicker'
|
|
3
|
+
import type { TPktInputSize } from 'shared-types/size'
|
|
3
4
|
|
|
4
5
|
export interface IPktDatepicker extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange'> {
|
|
5
6
|
id: string
|
|
@@ -23,6 +24,7 @@ export interface IPktDatepicker extends Omit<InputHTMLAttributes<HTMLInputElemen
|
|
|
23
24
|
|
|
24
25
|
// InputWrapper props
|
|
25
26
|
fullwidth?: boolean
|
|
27
|
+
inputSize?: TPktInputSize
|
|
26
28
|
hasFieldset?: boolean
|
|
27
29
|
inline?: boolean
|
|
28
30
|
helptext?: string | ReactNode
|
|
@@ -76,6 +78,7 @@ export interface IDatepickerState {
|
|
|
76
78
|
name?: string
|
|
77
79
|
placeholder?: string
|
|
78
80
|
fullwidth: boolean
|
|
81
|
+
inputSize: TPktInputSize
|
|
79
82
|
hasFieldset: boolean
|
|
80
83
|
inline: boolean
|
|
81
84
|
helptext?: string | ReactNode
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { defaultSingleStrings, defaultRangeStrings } from 'shared-types/datepicker'
|
|
3
|
+
import type { TPktInputSize } from 'shared-types/size'
|
|
3
4
|
import { valueToArray } from 'shared-utils/value-utils'
|
|
4
5
|
import { isIOS } from 'shared-utils/device-utils'
|
|
5
6
|
import { newDate, filterSelectableDates } from 'shared-utils/date-utils'
|
|
@@ -35,6 +36,7 @@ export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRe
|
|
|
35
36
|
calendarOpen: calendarOpenProp,
|
|
36
37
|
timezone,
|
|
37
38
|
fullwidth = false,
|
|
39
|
+
inputSize = 'medium' as TPktInputSize,
|
|
38
40
|
hasFieldset = false,
|
|
39
41
|
inline = false,
|
|
40
42
|
helptext,
|
|
@@ -110,20 +112,25 @@ export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRe
|
|
|
110
112
|
const maxStr = typeof max === 'string' ? max : max !== undefined ? String(max) : undefined
|
|
111
113
|
|
|
112
114
|
const inputClasses = useMemo(() => {
|
|
113
|
-
const classMap = getDatepickerInputClasses(
|
|
115
|
+
const classMap = getDatepickerInputClasses(
|
|
116
|
+
fullwidth,
|
|
117
|
+
showRangeLabels,
|
|
118
|
+
multiple,
|
|
119
|
+
range,
|
|
120
|
+
readOnly,
|
|
121
|
+
inputType,
|
|
122
|
+
inputSize,
|
|
123
|
+
)
|
|
114
124
|
return Object.entries(classMap)
|
|
115
125
|
.filter(([, v]) => v)
|
|
116
126
|
.map(([k]) => k)
|
|
117
127
|
.join(' ')
|
|
118
|
-
}, [fullwidth, showRangeLabels, multiple, range, readOnly, inputType])
|
|
128
|
+
}, [fullwidth, showRangeLabels, multiple, range, readOnly, inputType, inputSize])
|
|
119
129
|
|
|
120
|
-
const buttonClasses =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.map(([k]) => k)
|
|
125
|
-
.join(' ')
|
|
126
|
-
}, [])
|
|
130
|
+
const buttonClasses = Object.entries(getDatepickerButtonClasses(inputSize))
|
|
131
|
+
.filter(([, v]) => v)
|
|
132
|
+
.map(([k]) => k)
|
|
133
|
+
.join(' ')
|
|
127
134
|
|
|
128
135
|
const rangeLabelClasses = useMemo(() => {
|
|
129
136
|
const classMap = getRangeLabelClasses(showRangeLabels)
|
|
@@ -476,6 +483,7 @@ export function useDatepickerState(props: IPktDatepicker, ref: React.ForwardedRe
|
|
|
476
483
|
name,
|
|
477
484
|
placeholder,
|
|
478
485
|
fullwidth,
|
|
486
|
+
inputSize,
|
|
479
487
|
hasFieldset,
|
|
480
488
|
inline,
|
|
481
489
|
helptext,
|
|
@@ -61,11 +61,10 @@ export { formatFileSize, parseFileSize } from 'shared-utils/fileupload'
|
|
|
61
61
|
/**
|
|
62
62
|
* Shared props for `PktFileUpload` (both `form` and `custom` strategies).
|
|
63
63
|
*/
|
|
64
|
-
interface IBaseFileUploadProps
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
> {
|
|
64
|
+
interface IBaseFileUploadProps extends Omit<
|
|
65
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
66
|
+
'checked' | 'size' | 'type' | 'value' | 'onChange' | 'width' | 'defaultValue'
|
|
67
|
+
> {
|
|
69
68
|
/** Called with the next file list when files are added/removed/updated. */
|
|
70
69
|
onFilesChanged?: (files: TFileItemList) => void
|
|
71
70
|
/** Allow selecting/dropping multiple files. */
|
|
@@ -120,10 +119,20 @@ interface IBaseFileUploadProps
|
|
|
120
119
|
label?: string
|
|
121
120
|
/** Help text under the label. */
|
|
122
121
|
helptext?: string | ReactNode
|
|
123
|
-
/**
|
|
122
|
+
/** Expandable help text (dropdown). */
|
|
123
|
+
helptextDropdown?: string | ReactNode
|
|
124
|
+
/** Button label for expandable help text. */
|
|
125
|
+
helptextDropdownButton?: string
|
|
126
|
+
/** Shows an "optional" tag. */
|
|
124
127
|
optionalTag?: boolean
|
|
125
|
-
/**
|
|
128
|
+
/** Label text for the optional tag. */
|
|
129
|
+
optionalText?: string
|
|
130
|
+
/** Shows a "required" tag. */
|
|
126
131
|
requiredTag?: boolean
|
|
132
|
+
/** Label text for the required tag. */
|
|
133
|
+
requiredText?: string
|
|
134
|
+
/** Extra tag next to the label. */
|
|
135
|
+
tagText?: string | null
|
|
127
136
|
/** Marks the upload as required. Native validation is used in `form`; submit validation in `custom`. */
|
|
128
137
|
required?: boolean
|
|
129
138
|
/** Enable image preview modal (only applies to thumbnail renderer). */
|
|
@@ -131,8 +140,7 @@ interface IBaseFileUploadProps
|
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
interface IFileInputFileUploadProps
|
|
134
|
-
extends IBaseFileUploadProps,
|
|
135
|
-
Omit<HTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'defaultValue'> {
|
|
143
|
+
extends IBaseFileUploadProps, Omit<HTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'defaultValue'> {
|
|
136
144
|
uploadStrategy?: 'form'
|
|
137
145
|
onFileUploadRequested?: never
|
|
138
146
|
}
|
|
@@ -174,8 +182,13 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
|
|
|
174
182
|
disabled = false,
|
|
175
183
|
label,
|
|
176
184
|
helptext,
|
|
185
|
+
helptextDropdown,
|
|
186
|
+
helptextDropdownButton,
|
|
177
187
|
optionalTag,
|
|
188
|
+
optionalText,
|
|
178
189
|
requiredTag,
|
|
190
|
+
requiredText,
|
|
191
|
+
tagText,
|
|
179
192
|
required = false,
|
|
180
193
|
enableImagePreview = false,
|
|
181
194
|
['aria-describedby']: ariaDescribedByProp,
|
|
@@ -412,10 +425,15 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
|
|
|
412
425
|
forId={id}
|
|
413
426
|
label={label}
|
|
414
427
|
helptext={helptext}
|
|
428
|
+
helptextDropdown={helptextDropdown}
|
|
429
|
+
helptextDropdownButton={helptextDropdownButton}
|
|
415
430
|
disabled={disabled}
|
|
416
431
|
hasError={hasError}
|
|
417
432
|
optionalTag={optionalTag}
|
|
433
|
+
optionalText={optionalText}
|
|
418
434
|
requiredTag={requiredTag}
|
|
435
|
+
requiredText={requiredText}
|
|
436
|
+
tagText={tagText}
|
|
419
437
|
className={classNames('pkt-fileupload-wrapper', { 'pkt-fileupload-wrapper--full-width': fullwidth })}
|
|
420
438
|
>
|
|
421
439
|
{fileUploadContent}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
|
|
3
|
+
import { render } from '@testing-library/react'
|
|
4
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
5
|
+
import { describe, expect, test } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { EXAMPLE_HEADER_FOOTER_DATA } from 'shared-utils/header-footer'
|
|
8
|
+
import type { IPktFooterColumn, IPktFooterLink } from 'shared-types'
|
|
9
|
+
import { PktFooter } from './Footer'
|
|
10
|
+
import { PktFooterSimple } from '../footerSimple/FooterSimple'
|
|
11
|
+
|
|
12
|
+
expect.extend(toHaveNoViolations)
|
|
13
|
+
|
|
14
|
+
const COLUMNS: IPktFooterColumn[] = [
|
|
15
|
+
{
|
|
16
|
+
title: 'Overskrift 1',
|
|
17
|
+
text: 'Beskrivende tekst om løsningen.',
|
|
18
|
+
links: [{ text: 'Lenke 1', href: 'https://example.test/lenke-1' }],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
title: 'Overskrift 2',
|
|
22
|
+
links: [{ text: 'Lenke 2', href: 'https://example.test/lenke-2' }],
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
const LINKS: IPktFooterLink[] = [
|
|
27
|
+
{ text: 'Lenke 1', href: 'https://example.test/lenke-1' },
|
|
28
|
+
{ text: 'Lenke 2', href: 'https://example.test/lenke-2' },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
describe('PktFooter tilgjengelighet', () => {
|
|
32
|
+
test('kun global footer har ingen a11y-avvik', async () => {
|
|
33
|
+
const { container } = render(<PktFooter data={EXAMPLE_HEADER_FOOTER_DATA} />)
|
|
34
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('footer med custom kolonner har ingen a11y-avvik', async () => {
|
|
38
|
+
const { container } = render(<PktFooter columns={COLUMNS} data={EXAMPLE_HEADER_FOOTER_DATA} />)
|
|
39
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('kompakt footer har ingen a11y-avvik', async () => {
|
|
43
|
+
const { container } = render(<PktFooterSimple links={LINKS} data={EXAMPLE_HEADER_FOOTER_DATA} />)
|
|
44
|
+
expect(await axe(container)).toHaveNoViolations()
|
|
45
|
+
})
|
|
46
|
+
})
|