@oslokommune/punkt-react 13.22.0 → 14.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 +44 -0
- package/dist/index.d.ts +78 -15
- package/dist/punkt-react.es.js +5090 -3965
- package/dist/punkt-react.umd.js +749 -448
- package/package.json +5 -5
- package/src/components/header/Header.test.tsx +33 -27
- package/src/components/header/Header.tsx +23 -342
- package/src/components/header/HeaderService.test.tsx +515 -0
- package/src/components/header/HeaderService.tsx +525 -0
- package/src/components/header/types.ts +90 -0
- package/src/components/headerUserMenu/UserMenu.test.tsx +75 -0
- package/src/components/headerUserMenu/UserMenu.tsx +173 -0
- package/src/components/index.ts +1 -0
- package/src/components/interfaces.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.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,15 +39,15 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@lit-labs/ssr-dom-shim": "^1.2.1",
|
|
41
41
|
"@lit/react": "^1.0.7",
|
|
42
|
-
"@oslokommune/punkt-elements": "^
|
|
42
|
+
"@oslokommune/punkt-elements": "^14.0.0",
|
|
43
43
|
"classnames": "^2.5.1",
|
|
44
44
|
"prettier": "^3.3.3",
|
|
45
45
|
"react-hook-form": "^7.53.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@babel/plugin-transform-private-property-in-object": "^7.25.9",
|
|
49
|
-
"@oslokommune/punkt-assets": "^
|
|
50
|
-
"@oslokommune/punkt-css": "^
|
|
49
|
+
"@oslokommune/punkt-assets": "^14.0.0",
|
|
50
|
+
"@oslokommune/punkt-css": "^14.0.0",
|
|
51
51
|
"@testing-library/jest-dom": "^6.5.0",
|
|
52
52
|
"@testing-library/react": "^16.0.1",
|
|
53
53
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -103,5 +103,5 @@
|
|
|
103
103
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
104
104
|
},
|
|
105
105
|
"license": "MIT",
|
|
106
|
-
"gitHead": "
|
|
106
|
+
"gitHead": "65ed905c2410ac9ea347ede2e54a59b2fb1417cc"
|
|
107
107
|
}
|
|
@@ -3,15 +3,31 @@ import '@testing-library/jest-dom'
|
|
|
3
3
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
4
4
|
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
5
5
|
import React from 'react'
|
|
6
|
+
import { vi } from 'vitest'
|
|
6
7
|
|
|
7
8
|
import { PktHeader } from './Header'
|
|
8
9
|
|
|
9
10
|
expect.extend(toHaveNoViolations)
|
|
10
11
|
|
|
12
|
+
// Mock useWindowWidth hook to control viewport size
|
|
13
|
+
let mockedWidth = 1400
|
|
14
|
+
vi.mock('../../hooks/useWindowWidth', () => ({
|
|
15
|
+
useWindowWidth: () => mockedWidth,
|
|
16
|
+
}))
|
|
17
|
+
|
|
11
18
|
describe('PktHeader', () => {
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
// jsdom does not implement scrollTo; our scroll lock hook uses it
|
|
21
|
+
window.scrollTo = vi.fn()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
mockedWidth = 1400 // Reset to desktop by default
|
|
26
|
+
vi.clearAllMocks()
|
|
27
|
+
})
|
|
28
|
+
|
|
12
29
|
const mockUser = {
|
|
13
30
|
name: 'John Doe',
|
|
14
|
-
shortname: 'JD',
|
|
15
31
|
lastLoggedIn: '2023-08-20T12:34:56Z',
|
|
16
32
|
}
|
|
17
33
|
|
|
@@ -22,59 +38,49 @@ describe('PktHeader', () => {
|
|
|
22
38
|
|
|
23
39
|
const mockRepresenting = {
|
|
24
40
|
name: 'Org Name',
|
|
25
|
-
shortname: 'ON',
|
|
26
41
|
orgNumber: '123456789',
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Add assertions to check the presence of certain elements or classes
|
|
34
|
-
expect(screen.getByTestId('pkt-header')).toBeInTheDocument()
|
|
44
|
+
it('renders header with service name', () => {
|
|
45
|
+
const { container } = render(<PktHeader serviceName="Test Service" />)
|
|
46
|
+
expect(container.querySelector('.pkt-header-service')).toBeInTheDocument()
|
|
47
|
+
expect(screen.getByText('Test Service')).toBeInTheDocument()
|
|
35
48
|
})
|
|
36
49
|
|
|
37
50
|
it('renders user menu when user is present', () => {
|
|
38
|
-
render(<PktHeader user={mockUser} userMenu={mockUserMenu} />)
|
|
39
|
-
const userButton = screen.getByRole('button', { name:
|
|
51
|
+
render(<PktHeader serviceName="Test" user={mockUser} userMenu={mockUserMenu} />)
|
|
52
|
+
const userButton = screen.getByRole('button', { name: /John Doe/ })
|
|
40
53
|
fireEvent.click(userButton)
|
|
41
|
-
// Add assertions to check if user menu items are displayed
|
|
42
54
|
expect(screen.getByText('Profile')).toBeInTheDocument()
|
|
43
55
|
expect(screen.getByText('Settings')).toBeInTheDocument()
|
|
44
56
|
})
|
|
45
57
|
|
|
46
58
|
it('renders representing organization when representing is present', () => {
|
|
47
|
-
render(<PktHeader representing={mockRepresenting} />)
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
render(<PktHeader serviceName="Test" user={mockUser} representing={mockRepresenting} />)
|
|
60
|
+
const userButton = screen.getByRole('button', { name: /Org Name/ })
|
|
61
|
+
fireEvent.click(userButton)
|
|
50
62
|
expect(screen.getByText('Org.nr. 123456789')).toBeInTheDocument()
|
|
51
63
|
})
|
|
52
64
|
|
|
53
65
|
it('calls logOut function when Log Out button is clicked', async () => {
|
|
54
|
-
const mockLogOut =
|
|
55
|
-
render(<PktHeader
|
|
56
|
-
await window.customElements.whenDefined('pkt-button')
|
|
66
|
+
const mockLogOut = vi.fn()
|
|
67
|
+
render(<PktHeader serviceName="Test" logOutButtonPlacement="header" logOut={mockLogOut} />)
|
|
57
68
|
const logOutButton = screen.getByRole('button', { name: 'Logg ut' })
|
|
58
69
|
await fireEvent.click(logOutButton)
|
|
59
70
|
expect(mockLogOut).toHaveBeenCalled()
|
|
60
71
|
})
|
|
61
72
|
|
|
62
73
|
it('toggles user menu when user button is clicked', () => {
|
|
63
|
-
render(<PktHeader user={mockUser} userMenu={mockUserMenu}
|
|
64
|
-
const userButton = screen.getByRole('button', { name:
|
|
74
|
+
render(<PktHeader serviceName="Test" user={mockUser} userMenu={mockUserMenu} />)
|
|
75
|
+
const userButton = screen.getByRole('button', { name: /John Doe/ })
|
|
65
76
|
fireEvent.click(userButton)
|
|
66
|
-
//
|
|
67
|
-
expect(screen.
|
|
68
|
-
fireEvent.click(userButton)
|
|
69
|
-
// Add assertions to check if user menu is closed
|
|
70
|
-
expect(screen.getByTestId('usermenu').classList.contains('pkt-header--open-dropdown')).toBe(false)
|
|
77
|
+
// User menu should be open - check for menu items
|
|
78
|
+
expect(screen.getByText('Profile')).toBeInTheDocument()
|
|
71
79
|
})
|
|
72
80
|
|
|
73
81
|
describe('accessibility', () => {
|
|
74
82
|
it('renders with no wcag errors with axe', async () => {
|
|
75
|
-
const { container } = render(
|
|
76
|
-
<PktHeader user={mockUser} userMenu={mockUserMenu} userMenuFooter={mockUserMenuFooter} />,
|
|
77
|
-
)
|
|
83
|
+
const { container } = render(<PktHeader serviceName="Test" user={mockUser} userMenu={mockUserMenu} />)
|
|
78
84
|
const results = await axe(container)
|
|
79
85
|
expect(results).toHaveNoViolations()
|
|
80
86
|
})
|
|
@@ -1,344 +1,25 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export interface UserMenuFooterItem {
|
|
28
|
-
title: string
|
|
29
|
-
target: string | (() => void)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface IPktHeader extends HTMLAttributes<HTMLDivElement> {
|
|
33
|
-
logoLink?: string | (() => void)
|
|
34
|
-
serviceName?: string
|
|
35
|
-
fixed?: boolean
|
|
36
|
-
scrollToHide?: boolean
|
|
37
|
-
user?: User
|
|
38
|
-
userMenu?: UserMenuItem[]
|
|
39
|
-
representing?: Representing
|
|
40
|
-
userOptions?: UserMenuItem[]
|
|
41
|
-
userMenuFooter?: UserMenuFooterItem[]
|
|
42
|
-
canChangeRepresentation?: boolean
|
|
43
|
-
showMenuButton?: boolean
|
|
44
|
-
showLogOutButton?: boolean
|
|
45
|
-
openMenu?: () => void
|
|
46
|
-
logOut?: () => void
|
|
47
|
-
changeRepresentation?: () => void
|
|
48
|
-
children?: React.ReactNode | React.ReactNode[]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const PktHeader = forwardRef(
|
|
52
|
-
(
|
|
53
|
-
{
|
|
54
|
-
className,
|
|
55
|
-
logoLink = 'https://www.oslo.kommune.no/',
|
|
56
|
-
serviceName,
|
|
57
|
-
fixed = true,
|
|
58
|
-
scrollToHide = true,
|
|
59
|
-
user,
|
|
60
|
-
userMenu,
|
|
61
|
-
representing,
|
|
62
|
-
userOptions,
|
|
63
|
-
userMenuFooter,
|
|
64
|
-
canChangeRepresentation = true,
|
|
65
|
-
showMenuButton = false,
|
|
66
|
-
showLogOutButton = false,
|
|
67
|
-
openMenu,
|
|
68
|
-
logOut,
|
|
69
|
-
changeRepresentation,
|
|
70
|
-
children,
|
|
71
|
-
...props
|
|
72
|
-
}: IPktHeader,
|
|
73
|
-
ref: ForwardedRef<HTMLDivElement>,
|
|
74
|
-
) => {
|
|
75
|
-
const lastLoggedIn = React.useMemo(() => {
|
|
76
|
-
if (typeof user?.lastLoggedIn === 'string') {
|
|
77
|
-
return user.lastLoggedIn
|
|
78
|
-
}
|
|
79
|
-
return user?.lastLoggedIn
|
|
80
|
-
? new Date(user.lastLoggedIn).toLocaleString('nb-NO', {
|
|
81
|
-
year: 'numeric',
|
|
82
|
-
month: 'long',
|
|
83
|
-
day: 'numeric',
|
|
84
|
-
})
|
|
85
|
-
: ''
|
|
86
|
-
}, [user])
|
|
87
|
-
|
|
88
|
-
const [hidden, setHidden] = React.useState(false)
|
|
89
|
-
const [lastScrollPosition, setLastScrollPosition] = React.useState(0)
|
|
90
|
-
const [userMenuOpen, setUserMenuOpen] = React.useState(false)
|
|
91
|
-
|
|
92
|
-
const userMenuRef = React.useRef<HTMLLIElement>(null)
|
|
93
|
-
|
|
94
|
-
React.useEffect(() => {
|
|
95
|
-
if (document) {
|
|
96
|
-
document.addEventListener('mouseup', clickOutside)
|
|
97
|
-
window.addEventListener('scroll', onScroll)
|
|
98
|
-
}
|
|
99
|
-
return () => {
|
|
100
|
-
if (document) {
|
|
101
|
-
document.removeEventListener('mouseup', clickOutside)
|
|
102
|
-
window.removeEventListener('scroll', onScroll)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
const openUserMenu = () => {
|
|
108
|
-
setUserMenuOpen(!userMenuOpen)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const clickOutside = (e: MouseEvent) => {
|
|
112
|
-
if (userMenuRef.current && !userMenuRef.current.contains(e.target as HTMLElement)) {
|
|
113
|
-
setUserMenuOpen(false)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const onScroll = () => {
|
|
118
|
-
if (scrollToHide) {
|
|
119
|
-
const currentScrollPosition = window.pageYOffset || document.documentElement.scrollTop
|
|
120
|
-
if (currentScrollPosition < 0) {
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
if (Math.abs(currentScrollPosition - lastScrollPosition) < 60) {
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
setHidden(currentScrollPosition > lastScrollPosition)
|
|
127
|
-
setLastScrollPosition(currentScrollPosition)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<header
|
|
133
|
-
{...props}
|
|
134
|
-
id="pkt-header"
|
|
135
|
-
data-testid="pkt-header"
|
|
136
|
-
aria-label="Topp"
|
|
137
|
-
className={classNames(className, 'pkt-header', {
|
|
138
|
-
'pkt-header--fixed': fixed,
|
|
139
|
-
'pkt-header--scroll-to-hide': scrollToHide,
|
|
140
|
-
'pkt-header--hidden': hidden,
|
|
141
|
-
})}
|
|
142
|
-
ref={ref}
|
|
143
|
-
>
|
|
144
|
-
<div className="pkt-header__logo">
|
|
145
|
-
{typeof logoLink === 'string' ? (
|
|
146
|
-
<a aria-label="Tilbake til forside" className="pkt-header__logo-link" href={logoLink}>
|
|
147
|
-
<PktIcon
|
|
148
|
-
name="oslologo"
|
|
149
|
-
className="pkt-header__logo-svg"
|
|
150
|
-
aria-hidden="true"
|
|
151
|
-
path="https://punkt-cdn.oslo.kommune.no/latest/logos/"
|
|
152
|
-
></PktIcon>
|
|
153
|
-
</a>
|
|
154
|
-
) : (
|
|
155
|
-
<button
|
|
156
|
-
aria-label="Tilbake til forside"
|
|
157
|
-
className="pkt-link-button pkt-link pkt-header__logo-link"
|
|
158
|
-
onClick={logoLink}
|
|
159
|
-
>
|
|
160
|
-
<PktIcon
|
|
161
|
-
name="oslologo"
|
|
162
|
-
className="pkt-header__logo-svg"
|
|
163
|
-
aria-hidden="true"
|
|
164
|
-
path="https://punkt-cdn.oslo.kommune.no/latest/logos/"
|
|
165
|
-
></PktIcon>
|
|
166
|
-
</button>
|
|
167
|
-
)}
|
|
168
|
-
<span className="pkt-header__logo-service" translate="no">
|
|
169
|
-
{serviceName}
|
|
170
|
-
</span>
|
|
171
|
-
</div>
|
|
172
|
-
<nav className="pkt-header__actions">
|
|
173
|
-
<ul className="pkt-header__actions-row">
|
|
174
|
-
{showMenuButton && (
|
|
175
|
-
<li>
|
|
176
|
-
<PktButton
|
|
177
|
-
className="pkt-header__menu-btn"
|
|
178
|
-
skin="secondary"
|
|
179
|
-
variant="icon-right"
|
|
180
|
-
iconName="menu"
|
|
181
|
-
onClick={openMenu}
|
|
182
|
-
>
|
|
183
|
-
Meny
|
|
184
|
-
</PktButton>
|
|
185
|
-
</li>
|
|
186
|
-
)}
|
|
187
|
-
{(user || representing) && (
|
|
188
|
-
<li
|
|
189
|
-
data-testid="usermenu"
|
|
190
|
-
className={`pkt-header--has-dropdown ${userMenuOpen && !hidden ? 'pkt-header--open-dropdown' : ''}`}
|
|
191
|
-
ref={userMenuRef}
|
|
192
|
-
>
|
|
193
|
-
<button
|
|
194
|
-
className="pkt-header__user-btn pkt-btn pkt-btn--secondary pkt-btn--icons-right-and-left"
|
|
195
|
-
type="button"
|
|
196
|
-
role="button"
|
|
197
|
-
aria-controls="pktUserDropdown"
|
|
198
|
-
aria-expanded={userMenuOpen}
|
|
199
|
-
onClick={openUserMenu}
|
|
200
|
-
>
|
|
201
|
-
<PktIcon name="user" className="pkt-btn__icon" />
|
|
202
|
-
<span className="pkt-header__user-fullname" translate="no">
|
|
203
|
-
{representing?.name || user?.name}
|
|
204
|
-
</span>
|
|
205
|
-
<span className="pkt-header__user-shortname" translate="no">
|
|
206
|
-
{representing?.shortname || user?.shortname}
|
|
207
|
-
</span>
|
|
208
|
-
<PktIcon name="chevron-thin-down" className="pkt-btn--closed" />
|
|
209
|
-
<PktIcon name="chevron-thin-up" className="pkt-btn--open" />
|
|
210
|
-
</button>
|
|
211
|
-
<ul id="pktUserDropdown" className="pkt-header__dropdown pkt-user-menu">
|
|
212
|
-
{user && (
|
|
213
|
-
<li>
|
|
214
|
-
<div className="pkt-user-menu__label">Pålogget som</div>
|
|
215
|
-
<div className="pkt-user-menu__name" translate="no">
|
|
216
|
-
{user.name}
|
|
217
|
-
</div>
|
|
218
|
-
{user.lastLoggedIn && (
|
|
219
|
-
<div className="pkt-user-menu__last-logged-in">
|
|
220
|
-
Sist pålogget: <time>{lastLoggedIn}</time>
|
|
221
|
-
</div>
|
|
222
|
-
)}
|
|
223
|
-
</li>
|
|
224
|
-
)}
|
|
225
|
-
{userMenu && (
|
|
226
|
-
<li>
|
|
227
|
-
<ul className="pkt-list">
|
|
228
|
-
{userMenu.map((item, index) => (
|
|
229
|
-
<li key={`userMenu-${index}`}>
|
|
230
|
-
{typeof item.target === 'string' ? (
|
|
231
|
-
<a href={item.target} className="pkt-link">
|
|
232
|
-
{item.iconName && <PktIcon name={item.iconName} className="pkt-link__icon" />}
|
|
233
|
-
{item.title}
|
|
234
|
-
</a>
|
|
235
|
-
) : (
|
|
236
|
-
<button className="pkt-link-button pkt-link" onClick={item.target}>
|
|
237
|
-
{item.iconName && <PktIcon name={item.iconName} className="pkt-link__icon" />}
|
|
238
|
-
{item.title}
|
|
239
|
-
</button>
|
|
240
|
-
)}
|
|
241
|
-
</li>
|
|
242
|
-
))}
|
|
243
|
-
</ul>
|
|
244
|
-
</li>
|
|
245
|
-
)}
|
|
246
|
-
{(representing || canChangeRepresentation) && (
|
|
247
|
-
<li>
|
|
248
|
-
{representing && (
|
|
249
|
-
<>
|
|
250
|
-
<div className="pkt-user-menu__label">Representerer</div>
|
|
251
|
-
<div className="pkt-user-menu__name" translate="no">
|
|
252
|
-
{representing.name}
|
|
253
|
-
</div>
|
|
254
|
-
{representing.orgNumber && (
|
|
255
|
-
<div className="pkt-user-menu__org-number">Org.nr. {representing.orgNumber}</div>
|
|
256
|
-
)}
|
|
257
|
-
</>
|
|
258
|
-
)}
|
|
259
|
-
<ul className="pkt-list mt-size-16">
|
|
260
|
-
{canChangeRepresentation && (
|
|
261
|
-
<li>
|
|
262
|
-
<button className="pkt-link-button pkt-link" onClick={changeRepresentation}>
|
|
263
|
-
<PktIcon name="cogwheel" className="pkt-link__icon" />
|
|
264
|
-
Endre organisasjon
|
|
265
|
-
</button>
|
|
266
|
-
</li>
|
|
267
|
-
)}
|
|
268
|
-
</ul>
|
|
269
|
-
</li>
|
|
270
|
-
)}
|
|
271
|
-
<li>
|
|
272
|
-
<ul className="pkt-list">
|
|
273
|
-
{(userOptions || !showLogOutButton) && (
|
|
274
|
-
<>
|
|
275
|
-
{userOptions?.map((item, index) => (
|
|
276
|
-
<li key={`userOptions-${index}`}>
|
|
277
|
-
{typeof item.target === 'string' ? (
|
|
278
|
-
<a href={item.target} className="pkt-link">
|
|
279
|
-
{item.iconName && <PktIcon name={item.iconName} className="pkt-link__icon" />}
|
|
280
|
-
{item.title}
|
|
281
|
-
</a>
|
|
282
|
-
) : (
|
|
283
|
-
<button className="pkt-link-button pkt-link" onClick={item.target}>
|
|
284
|
-
{item.iconName && <PktIcon name={item.iconName} className="pkt-link__icon" />}
|
|
285
|
-
{item.title}
|
|
286
|
-
</button>
|
|
287
|
-
)}
|
|
288
|
-
</li>
|
|
289
|
-
))}
|
|
290
|
-
{!showLogOutButton && (
|
|
291
|
-
<li>
|
|
292
|
-
<button className="pkt-link-button pkt-link" onClick={logOut}>
|
|
293
|
-
<PktIcon name="exit" className="pkt-link__icon" />
|
|
294
|
-
Logg ut
|
|
295
|
-
</button>
|
|
296
|
-
</li>
|
|
297
|
-
)}
|
|
298
|
-
</>
|
|
299
|
-
)}
|
|
300
|
-
</ul>
|
|
301
|
-
</li>
|
|
302
|
-
{userMenuFooter && (
|
|
303
|
-
<li className="footer">
|
|
304
|
-
<ul className="pkt-list-horizontal bordered">
|
|
305
|
-
{userMenuFooter.map((item, index) => (
|
|
306
|
-
<li key={`userMenuFooter-${index}`}>
|
|
307
|
-
{typeof item.target === 'string' ? (
|
|
308
|
-
<a href={item.target} className="pkt-link">
|
|
309
|
-
{item.title}
|
|
310
|
-
</a>
|
|
311
|
-
) : (
|
|
312
|
-
<button className="pkt-link-button pkt-link" onClick={item.target}>
|
|
313
|
-
{item.title}
|
|
314
|
-
</button>
|
|
315
|
-
)}
|
|
316
|
-
</li>
|
|
317
|
-
))}
|
|
318
|
-
</ul>
|
|
319
|
-
</li>
|
|
320
|
-
)}
|
|
321
|
-
</ul>
|
|
322
|
-
</li>
|
|
323
|
-
)}
|
|
324
|
-
{children && <li>{children}</li>}
|
|
325
|
-
{showLogOutButton && (
|
|
326
|
-
<li>
|
|
327
|
-
<PktButton
|
|
328
|
-
className="pkt-header__user-btn pkt-header__user-btn-logout"
|
|
329
|
-
iconName="exit"
|
|
330
|
-
role="button"
|
|
331
|
-
onClick={logOut}
|
|
332
|
-
skin="secondary"
|
|
333
|
-
variant="icon-right"
|
|
334
|
-
>
|
|
335
|
-
Logg ut
|
|
336
|
-
</PktButton>
|
|
337
|
-
</li>
|
|
338
|
-
)}
|
|
339
|
-
</ul>
|
|
340
|
-
</nav>
|
|
341
|
-
</header>
|
|
342
|
-
)
|
|
343
|
-
},
|
|
344
|
-
)
|
|
3
|
+
import { ForwardedRef, forwardRef } from 'react'
|
|
4
|
+
import { PktHeaderService } from './HeaderService'
|
|
5
|
+
import { IPktHeader } from './types'
|
|
6
|
+
|
|
7
|
+
export * from './types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* PktHeader - Main header component for Oslo kommune services
|
|
11
|
+
*
|
|
12
|
+
* This component provides a complete header solution with:
|
|
13
|
+
* - Logo and service name
|
|
14
|
+
* - User menu with login/logout functionality
|
|
15
|
+
* - Search functionality
|
|
16
|
+
* - Responsive mobile menu
|
|
17
|
+
* - Fixed positioning with scroll-to-hide
|
|
18
|
+
*
|
|
19
|
+
* TODO: Add `type` prop to switch between `service` and `global` header types
|
|
20
|
+
*/
|
|
21
|
+
export const PktHeader = forwardRef((props: IPktHeader, ref: ForwardedRef<HTMLDivElement>) => {
|
|
22
|
+
return <PktHeaderService {...props} ref={ref} />
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
PktHeader.displayName = 'PktHeader'
|