@oslokommune/punkt-react 13.21.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 +5092 -3965
- package/dist/punkt-react.umd.js +751 -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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ForwardedRef, forwardRef } from 'react'
|
|
4
|
+
import { PktIcon } from '../icon/Icon'
|
|
5
|
+
import { PktLink } from '../link/Link'
|
|
6
|
+
import { User, Representing, UserMenuItem } from '../header/types'
|
|
7
|
+
import classNames from 'classnames'
|
|
8
|
+
|
|
9
|
+
// Internal type for rendering links/buttons (supports both href and onClick)
|
|
10
|
+
interface IInternalMenuItemBase {
|
|
11
|
+
title: string
|
|
12
|
+
iconName?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface InternalMenuLink extends IInternalMenuItemBase {
|
|
16
|
+
href: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface InternalMenuButton extends IInternalMenuItemBase {
|
|
20
|
+
onClick: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type TInternalMenuItem = InternalMenuLink | InternalMenuButton
|
|
24
|
+
|
|
25
|
+
// Helper to convert UserMenuItem (with target) to internal format
|
|
26
|
+
const convertUserMenuItem = (item: UserMenuItem): TInternalMenuItem => {
|
|
27
|
+
if (typeof item.target === 'string') {
|
|
28
|
+
return { title: item.title, iconName: item.iconName, href: item.target }
|
|
29
|
+
}
|
|
30
|
+
return { title: item.title, iconName: item.iconName, onClick: item.target }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface IPktHeaderUserMenu {
|
|
34
|
+
user: User
|
|
35
|
+
formattedLastLoggedIn?: string
|
|
36
|
+
representing?: Representing
|
|
37
|
+
userMenu?: UserMenuItem[]
|
|
38
|
+
canChangeRepresentation?: boolean
|
|
39
|
+
changeRepresentation?: () => void
|
|
40
|
+
logoutOnClick?: () => void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const LinkOrButtonComponent = ({ item, className }: { item: TInternalMenuItem; className?: string }) => {
|
|
44
|
+
const isLink = 'href' in item
|
|
45
|
+
|
|
46
|
+
return isLink ? (
|
|
47
|
+
<PktLink
|
|
48
|
+
iconName={item.iconName}
|
|
49
|
+
href={item.href}
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
className={classNames('pkt-user-menu__link', className)}
|
|
52
|
+
>
|
|
53
|
+
{item.title}
|
|
54
|
+
</PktLink>
|
|
55
|
+
) : (
|
|
56
|
+
<button
|
|
57
|
+
className={classNames('pkt-user-menu__link pkt-link-button pkt-link pkt-link--icon-left', className)}
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={() => {
|
|
60
|
+
if ('onClick' in item && typeof item.onClick === 'function') {
|
|
61
|
+
item.onClick()
|
|
62
|
+
} else {
|
|
63
|
+
console.error('UserMenuButton item is missing onClick handler or onClick is not a function:', item)
|
|
64
|
+
}
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{item.iconName && <PktIcon name={item.iconName} className="pkt-link__icon" aria-hidden="true" />}
|
|
68
|
+
{item.title}
|
|
69
|
+
</button>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const LinkSection = ({ links }: { links: TInternalMenuItem[] }) => {
|
|
74
|
+
return (
|
|
75
|
+
<ul className="pkt-user-menu__sublist">
|
|
76
|
+
{links.map((item, index) => {
|
|
77
|
+
return (
|
|
78
|
+
<li key={index} className="pkt-user-menu__subitem">
|
|
79
|
+
<LinkOrButtonComponent item={item} />
|
|
80
|
+
</li>
|
|
81
|
+
)
|
|
82
|
+
})}
|
|
83
|
+
</ul>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const PktHeaderUserMenu = forwardRef(
|
|
88
|
+
(
|
|
89
|
+
{
|
|
90
|
+
user,
|
|
91
|
+
formattedLastLoggedIn,
|
|
92
|
+
representing,
|
|
93
|
+
userMenu,
|
|
94
|
+
canChangeRepresentation,
|
|
95
|
+
changeRepresentation,
|
|
96
|
+
logoutOnClick,
|
|
97
|
+
}: IPktHeaderUserMenu,
|
|
98
|
+
ref: ForwardedRef<HTMLDivElement>,
|
|
99
|
+
) => {
|
|
100
|
+
// Convert userMenu items to internal format
|
|
101
|
+
const internalMenuItems = userMenu?.map(convertUserMenuItem)
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<nav className="pkt-user-menu" ref={ref} aria-label="Meny for innlogget bruker">
|
|
105
|
+
<ul className="pkt-user-menu__list">
|
|
106
|
+
{/* User section */}
|
|
107
|
+
{user && (
|
|
108
|
+
<li className="pkt-user-menu__item">
|
|
109
|
+
<div className="pkt-user-menu__label">Pålogget som</div>
|
|
110
|
+
<div className="pkt-user-menu__name" translate="no">
|
|
111
|
+
{user.name}
|
|
112
|
+
</div>
|
|
113
|
+
{formattedLastLoggedIn && (
|
|
114
|
+
<div className="pkt-user-menu__last-logged-in">
|
|
115
|
+
Sist pålogget: <time>{formattedLastLoggedIn}</time>
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
</li>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{/* User menu items */}
|
|
122
|
+
{internalMenuItems && internalMenuItems.length > 0 && (
|
|
123
|
+
<li className="pkt-user-menu__item">
|
|
124
|
+
<LinkSection links={internalMenuItems} />
|
|
125
|
+
</li>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{/* Representing section */}
|
|
129
|
+
{representing && (
|
|
130
|
+
<li className="pkt-user-menu__item">
|
|
131
|
+
<div className="pkt-user-menu__label">Representerer</div>
|
|
132
|
+
<div className="pkt-user-menu__name" translate="no">
|
|
133
|
+
{representing.name}
|
|
134
|
+
</div>
|
|
135
|
+
{representing.orgNumber && (
|
|
136
|
+
<div className="pkt-user-menu__org-number">Org.nr. {representing.orgNumber}</div>
|
|
137
|
+
)}
|
|
138
|
+
{canChangeRepresentation && changeRepresentation && (
|
|
139
|
+
<ul className="pkt-user-menu__sublist mt-size-16">
|
|
140
|
+
<li className="pkt-user-menu__subitem">
|
|
141
|
+
<LinkOrButtonComponent
|
|
142
|
+
item={{ title: 'Endre organisasjon', iconName: 'cogwheel', onClick: changeRepresentation }}
|
|
143
|
+
/>
|
|
144
|
+
</li>
|
|
145
|
+
</ul>
|
|
146
|
+
)}
|
|
147
|
+
</li>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Change representation without representing object */}
|
|
151
|
+
{!representing && canChangeRepresentation && changeRepresentation && (
|
|
152
|
+
<li className="pkt-user-menu__item">
|
|
153
|
+
<ul className="pkt-user-menu__sublist">
|
|
154
|
+
<li className="pkt-user-menu__subitem">
|
|
155
|
+
<LinkOrButtonComponent
|
|
156
|
+
item={{ title: 'Endre organisasjon', iconName: 'cogwheel', onClick: changeRepresentation }}
|
|
157
|
+
/>
|
|
158
|
+
</li>
|
|
159
|
+
</ul>
|
|
160
|
+
</li>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{/* Logout */}
|
|
164
|
+
{logoutOnClick && (
|
|
165
|
+
<li className="pkt-user-menu__item">
|
|
166
|
+
<LinkOrButtonComponent item={{ title: 'Logg ut', iconName: 'exit', onClick: logoutOnClick }} />
|
|
167
|
+
</li>
|
|
168
|
+
)}
|
|
169
|
+
</ul>
|
|
170
|
+
</nav>
|
|
171
|
+
)
|
|
172
|
+
},
|
|
173
|
+
)
|
package/src/components/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { PktDatepicker } from './datepicker/Datepicker'
|
|
|
12
12
|
export { PktFooter } from './footer/Footer'
|
|
13
13
|
export { PktFooterSimple } from './footerSimple/FooterSimple'
|
|
14
14
|
export { PktHeader } from './header/Header'
|
|
15
|
+
export { PktHeaderService } from './header/HeaderService'
|
|
15
16
|
export { PktHeading } from './heading/Heading'
|
|
16
17
|
export { PktHelptext } from './helptext/Helptext'
|
|
17
18
|
export { PktIcon } from './icon/Icon'
|
|
@@ -11,6 +11,7 @@ export type { IPktDatepicker } from './datepicker/Datepicker'
|
|
|
11
11
|
export type { IPktFooter } from './footer/Footer'
|
|
12
12
|
export type { IPktFooterSimple } from './footerSimple/FooterSimple'
|
|
13
13
|
export type { IPktHeader } from './header/Header'
|
|
14
|
+
export type { IPktHeaderService } from './header/HeaderService'
|
|
14
15
|
export type { IPktInputWrapper } from './inputwrapper/InputWrapper'
|
|
15
16
|
export type { IPktLinkCard } from './linkcard/LinkCard'
|
|
16
17
|
export type { IPktLoader } from './loader/Loader'
|