@oslokommune/punkt-elements 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/header.d.ts +1 -0
- package/dist/index.d.ts +259 -1
- package/dist/pkt-header.cjs +302 -0
- package/dist/pkt-header.js +852 -0
- package/dist/pkt-index.cjs +5 -5
- package/dist/pkt-index.js +62 -58
- package/package.json +4 -4
- package/src/components/header/header-service.test.ts +404 -0
- package/src/components/header/header-service.ts +762 -0
- package/src/components/header/header-user-menu.ts +215 -0
- package/src/components/header/header-utils.ts +20 -0
- package/src/components/header/header.ts +141 -0
- package/src/components/header/index.ts +15 -0
- package/src/components/header/types.ts +151 -0
- package/src/components/index.ts +10 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { PktElement } from '@/base-elements/element'
|
|
2
|
+
import { html, nothing } from 'lit'
|
|
3
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
4
|
+
import { classMap } from 'lit/directives/class-map.js'
|
|
5
|
+
import {
|
|
6
|
+
User,
|
|
7
|
+
Representing,
|
|
8
|
+
UserMenuItem,
|
|
9
|
+
TInternalMenuItem,
|
|
10
|
+
convertUserMenuItem,
|
|
11
|
+
Booleanish,
|
|
12
|
+
booleanishConverter,
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
import '@/components/icon'
|
|
16
|
+
import '@/components/link'
|
|
17
|
+
|
|
18
|
+
export interface IPktHeaderUserMenu {
|
|
19
|
+
user: User
|
|
20
|
+
formattedLastLoggedIn?: string
|
|
21
|
+
representing?: Representing
|
|
22
|
+
userMenu?: UserMenuItem[]
|
|
23
|
+
canChangeRepresentation?: Booleanish
|
|
24
|
+
logoutOnClick?: Booleanish
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@customElement('pkt-header-user-menu')
|
|
28
|
+
export class PktHeaderUserMenu
|
|
29
|
+
extends PktElement<IPktHeaderUserMenu>
|
|
30
|
+
implements IPktHeaderUserMenu
|
|
31
|
+
{
|
|
32
|
+
@property({ type: Object }) user!: User
|
|
33
|
+
@property({ type: String, attribute: 'formatted-last-logged-in' }) formattedLastLoggedIn?: string
|
|
34
|
+
@property({ type: Object }) representing?: Representing
|
|
35
|
+
@property({ type: Array, attribute: 'user-menu' }) userMenu?: UserMenuItem[]
|
|
36
|
+
@property({
|
|
37
|
+
type: Boolean,
|
|
38
|
+
attribute: 'can-change-representation',
|
|
39
|
+
converter: booleanishConverter,
|
|
40
|
+
})
|
|
41
|
+
canChangeRepresentation: Booleanish = false
|
|
42
|
+
@property({ type: Boolean, attribute: 'logout-on-click', converter: booleanishConverter })
|
|
43
|
+
logoutOnClick: Booleanish = false
|
|
44
|
+
|
|
45
|
+
private handleChangeRepresentation() {
|
|
46
|
+
this.dispatchEvent(
|
|
47
|
+
new CustomEvent('change-representation', {
|
|
48
|
+
bubbles: true,
|
|
49
|
+
composed: true,
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private handleLogout() {
|
|
55
|
+
this.dispatchEvent(
|
|
56
|
+
new CustomEvent('log-out', {
|
|
57
|
+
bubbles: true,
|
|
58
|
+
composed: true,
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private handleMenuItemClick(item: TInternalMenuItem) {
|
|
64
|
+
if ('onClick' in item && typeof item.onClick === 'function') {
|
|
65
|
+
item.onClick()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private renderLinkOrButton(item: TInternalMenuItem, className?: string) {
|
|
70
|
+
const isLink = 'href' in item
|
|
71
|
+
const classes = classMap({
|
|
72
|
+
'pkt-user-menu__link': true,
|
|
73
|
+
'pkt-link-button': !isLink,
|
|
74
|
+
'pkt-link': !isLink,
|
|
75
|
+
'pkt-link--icon-left': !isLink,
|
|
76
|
+
[className || '']: !!className,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (isLink) {
|
|
80
|
+
return html`
|
|
81
|
+
<pkt-link
|
|
82
|
+
icon-name=${item.iconName || nothing}
|
|
83
|
+
href=${item.href}
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
class="pkt-user-menu__link ${className || ''}"
|
|
86
|
+
>
|
|
87
|
+
${item.title}
|
|
88
|
+
</pkt-link>
|
|
89
|
+
`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return html`
|
|
93
|
+
<button class=${classes} type="button" @click=${() => this.handleMenuItemClick(item)}>
|
|
94
|
+
${item.iconName
|
|
95
|
+
? html`<pkt-icon
|
|
96
|
+
name=${item.iconName}
|
|
97
|
+
class="pkt-link__icon"
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
></pkt-icon>`
|
|
100
|
+
: nothing}
|
|
101
|
+
${item.title}
|
|
102
|
+
</button>
|
|
103
|
+
`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private renderLinkSection(links: TInternalMenuItem[]) {
|
|
107
|
+
return html`
|
|
108
|
+
<ul class="pkt-user-menu__sublist">
|
|
109
|
+
${links.map(
|
|
110
|
+
(item) => html`
|
|
111
|
+
<li class="pkt-user-menu__subitem">${this.renderLinkOrButton(item)}</li>
|
|
112
|
+
`,
|
|
113
|
+
)}
|
|
114
|
+
</ul>
|
|
115
|
+
`
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
render() {
|
|
119
|
+
const internalMenuItems = this.userMenu?.map(convertUserMenuItem)
|
|
120
|
+
|
|
121
|
+
return html`
|
|
122
|
+
<nav class="pkt-user-menu" aria-label="Meny for innlogget bruker">
|
|
123
|
+
<ul class="pkt-user-menu__list">
|
|
124
|
+
<!-- User section -->
|
|
125
|
+
${this.user
|
|
126
|
+
? html`
|
|
127
|
+
<li class="pkt-user-menu__item">
|
|
128
|
+
<div class="pkt-user-menu__label">Pålogget som</div>
|
|
129
|
+
<div class="pkt-user-menu__name" translate="no">${this.user.name}</div>
|
|
130
|
+
${this.formattedLastLoggedIn
|
|
131
|
+
? html`
|
|
132
|
+
<div class="pkt-user-menu__last-logged-in">
|
|
133
|
+
Sist pålogget: <time>${this.formattedLastLoggedIn}</time>
|
|
134
|
+
</div>
|
|
135
|
+
`
|
|
136
|
+
: nothing}
|
|
137
|
+
</li>
|
|
138
|
+
`
|
|
139
|
+
: nothing}
|
|
140
|
+
|
|
141
|
+
<!-- User menu items -->
|
|
142
|
+
${internalMenuItems && internalMenuItems.length > 0
|
|
143
|
+
? html`
|
|
144
|
+
<li class="pkt-user-menu__item">${this.renderLinkSection(internalMenuItems)}</li>
|
|
145
|
+
`
|
|
146
|
+
: nothing}
|
|
147
|
+
|
|
148
|
+
<!-- Representing section -->
|
|
149
|
+
${this.representing
|
|
150
|
+
? html`
|
|
151
|
+
<li class="pkt-user-menu__item">
|
|
152
|
+
<div class="pkt-user-menu__label">Representerer</div>
|
|
153
|
+
<div class="pkt-user-menu__name" translate="no">${this.representing.name}</div>
|
|
154
|
+
${this.representing.orgNumber
|
|
155
|
+
? html`<div class="pkt-user-menu__org-number">
|
|
156
|
+
Org.nr. ${this.representing.orgNumber}
|
|
157
|
+
</div>`
|
|
158
|
+
: nothing}
|
|
159
|
+
${this.canChangeRepresentation
|
|
160
|
+
? html`
|
|
161
|
+
<ul class="pkt-user-menu__sublist mt-size-16">
|
|
162
|
+
<li class="pkt-user-menu__subitem">
|
|
163
|
+
${this.renderLinkOrButton({
|
|
164
|
+
title: 'Endre organisasjon',
|
|
165
|
+
iconName: 'cogwheel',
|
|
166
|
+
onClick: () => this.handleChangeRepresentation(),
|
|
167
|
+
})}
|
|
168
|
+
</li>
|
|
169
|
+
</ul>
|
|
170
|
+
`
|
|
171
|
+
: nothing}
|
|
172
|
+
</li>
|
|
173
|
+
`
|
|
174
|
+
: nothing}
|
|
175
|
+
|
|
176
|
+
<!-- Change representation without representing object -->
|
|
177
|
+
${!this.representing && this.canChangeRepresentation
|
|
178
|
+
? html`
|
|
179
|
+
<li class="pkt-user-menu__item">
|
|
180
|
+
<ul class="pkt-user-menu__sublist">
|
|
181
|
+
<li class="pkt-user-menu__subitem">
|
|
182
|
+
${this.renderLinkOrButton({
|
|
183
|
+
title: 'Endre organisasjon',
|
|
184
|
+
iconName: 'cogwheel',
|
|
185
|
+
onClick: () => this.handleChangeRepresentation(),
|
|
186
|
+
})}
|
|
187
|
+
</li>
|
|
188
|
+
</ul>
|
|
189
|
+
</li>
|
|
190
|
+
`
|
|
191
|
+
: nothing}
|
|
192
|
+
|
|
193
|
+
<!-- Logout -->
|
|
194
|
+
${this.logoutOnClick
|
|
195
|
+
? html`
|
|
196
|
+
<li class="pkt-user-menu__item">
|
|
197
|
+
${this.renderLinkOrButton({
|
|
198
|
+
title: 'Logg ut',
|
|
199
|
+
iconName: 'exit',
|
|
200
|
+
onClick: () => this.handleLogout(),
|
|
201
|
+
})}
|
|
202
|
+
</li>
|
|
203
|
+
`
|
|
204
|
+
: nothing}
|
|
205
|
+
</ul>
|
|
206
|
+
</nav>
|
|
207
|
+
`
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
declare global {
|
|
212
|
+
interface HTMLElementTagNameMap {
|
|
213
|
+
'pkt-header-user-menu': PktHeaderUserMenu
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a Date or ISO string to a readable Norwegian date format
|
|
3
|
+
* @param date - Date object or ISO date string
|
|
4
|
+
* @returns Formatted date string like "22. januar 2026, 14:30"
|
|
5
|
+
*/
|
|
6
|
+
export const formatLastLoggedIn = (date: Date | string | undefined): string | undefined => {
|
|
7
|
+
if (!date) return undefined
|
|
8
|
+
|
|
9
|
+
const dateObj = typeof date === 'string' ? new Date(date) : date
|
|
10
|
+
|
|
11
|
+
if (isNaN(dateObj.getTime())) return undefined
|
|
12
|
+
|
|
13
|
+
return dateObj.toLocaleDateString('nb-NO', {
|
|
14
|
+
day: 'numeric',
|
|
15
|
+
month: 'long',
|
|
16
|
+
year: 'numeric',
|
|
17
|
+
hour: '2-digit',
|
|
18
|
+
minute: '2-digit',
|
|
19
|
+
})
|
|
20
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { PktElement } from '@/base-elements/element'
|
|
2
|
+
import { PktSlotController } from '@/controllers/pkt-slot-controller'
|
|
3
|
+
import { html, PropertyValues } from 'lit'
|
|
4
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
5
|
+
import { ifDefined } from 'lit/directives/if-defined.js'
|
|
6
|
+
import { createRef, Ref, ref } from 'lit/directives/ref.js'
|
|
7
|
+
import {
|
|
8
|
+
User,
|
|
9
|
+
Representing,
|
|
10
|
+
UserMenuItem,
|
|
11
|
+
TPktHeaderMenu,
|
|
12
|
+
TLogOutButtonPlacement,
|
|
13
|
+
THeaderPosition,
|
|
14
|
+
THeaderScrollBehavior,
|
|
15
|
+
IPktHeader,
|
|
16
|
+
Booleanish,
|
|
17
|
+
booleanishConverter,
|
|
18
|
+
} from './types'
|
|
19
|
+
|
|
20
|
+
import './header-service'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* PktHeader - Main header component for Oslo kommune services
|
|
24
|
+
*
|
|
25
|
+
* This component provides a complete header solution with:
|
|
26
|
+
* - Logo and service name
|
|
27
|
+
* - User menu with login/logout functionality
|
|
28
|
+
* - Search functionality
|
|
29
|
+
* - Responsive mobile menu
|
|
30
|
+
* - Fixed positioning with scroll-to-hide
|
|
31
|
+
*
|
|
32
|
+
* TODO: Add `type` prop to switch between `service` and `global` header types
|
|
33
|
+
*/
|
|
34
|
+
@customElement('pkt-header')
|
|
35
|
+
export class PktHeader extends PktElement<IPktHeader> implements IPktHeader {
|
|
36
|
+
defaultSlot: Ref<HTMLElement> = createRef()
|
|
37
|
+
slotController!: PktSlotController
|
|
38
|
+
|
|
39
|
+
@property({ type: String, attribute: 'service-name' }) serviceName?: string
|
|
40
|
+
@property({ type: String, attribute: 'service-link' }) serviceLink?: string
|
|
41
|
+
@property({ type: String, attribute: 'logo-link' }) logoLink?: string
|
|
42
|
+
@property({ type: String, attribute: 'search-placeholder' }) searchPlaceholder = 'Søk'
|
|
43
|
+
@property({ type: String, attribute: 'search-value' }) searchValue = ''
|
|
44
|
+
@property({ type: Number, attribute: 'mobile-breakpoint' }) mobileBreakpoint: number = 768
|
|
45
|
+
@property({ type: Number, attribute: 'tablet-breakpoint' }) tabletBreakpoint: number = 1280
|
|
46
|
+
@property({ type: String, attribute: 'opened-menu' }) openedMenu: TPktHeaderMenu = 'none'
|
|
47
|
+
@property({ type: String, attribute: 'log-out-button-placement' })
|
|
48
|
+
logOutButtonPlacement: TLogOutButtonPlacement = 'none'
|
|
49
|
+
@property({ type: String }) position: THeaderPosition = 'fixed'
|
|
50
|
+
@property({ type: String, attribute: 'scroll-behavior' }) scrollBehavior: THeaderScrollBehavior =
|
|
51
|
+
'hide'
|
|
52
|
+
|
|
53
|
+
@property({ type: Boolean, attribute: 'hide-logo', converter: booleanishConverter })
|
|
54
|
+
hideLogo: Booleanish = false
|
|
55
|
+
@property({ type: Boolean, converter: booleanishConverter }) compact: Booleanish = false
|
|
56
|
+
@property({ type: Boolean, attribute: 'show-search', converter: booleanishConverter })
|
|
57
|
+
showSearch: Booleanish = false
|
|
58
|
+
@property({
|
|
59
|
+
type: Boolean,
|
|
60
|
+
attribute: 'can-change-representation',
|
|
61
|
+
converter: booleanishConverter,
|
|
62
|
+
})
|
|
63
|
+
canChangeRepresentation: Booleanish = false
|
|
64
|
+
@property({ type: Boolean, attribute: 'has-log-out', converter: booleanishConverter })
|
|
65
|
+
hasLogOut: Booleanish = false
|
|
66
|
+
|
|
67
|
+
@property({ type: Object }) user?: User
|
|
68
|
+
@property({ type: Array, attribute: 'user-menu' }) userMenu?: UserMenuItem[]
|
|
69
|
+
@property({ type: Object }) representing?: Representing
|
|
70
|
+
|
|
71
|
+
// Deprecated props - emit warnings
|
|
72
|
+
@property({ type: Array, attribute: 'user-menu-footer' }) userMenuFooter?: UserMenuItem[]
|
|
73
|
+
@property({ type: Array, attribute: 'user-options' }) userOptions?: UserMenuItem[]
|
|
74
|
+
|
|
75
|
+
constructor() {
|
|
76
|
+
super()
|
|
77
|
+
this.slotController = new PktSlotController(this, this.defaultSlot)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
firstUpdated(changedProperties: PropertyValues) {
|
|
81
|
+
super.firstUpdated(changedProperties)
|
|
82
|
+
this.emitDeprecationWarnings()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private emitDeprecationWarnings() {
|
|
86
|
+
if (this.userMenuFooter !== undefined) {
|
|
87
|
+
console.warn('[PktHeader] userMenuFooter is deprecated. Use userMenu instead.')
|
|
88
|
+
}
|
|
89
|
+
if (this.userOptions !== undefined) {
|
|
90
|
+
console.warn('[PktHeader] userOptions is deprecated. Use userMenu instead.')
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Convert deprecated props to new props
|
|
96
|
+
*/
|
|
97
|
+
private get effectiveUserMenu(): UserMenuItem[] | undefined {
|
|
98
|
+
const menu = this.userMenu || []
|
|
99
|
+
const footer = this.userMenuFooter || []
|
|
100
|
+
const options = this.userOptions || []
|
|
101
|
+
|
|
102
|
+
if (footer.length || options.length) {
|
|
103
|
+
return [...menu, ...options, ...footer]
|
|
104
|
+
}
|
|
105
|
+
return this.userMenu
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
render() {
|
|
109
|
+
return html`
|
|
110
|
+
<pkt-header-service
|
|
111
|
+
service-name=${ifDefined(this.serviceName)}
|
|
112
|
+
service-link=${ifDefined(this.serviceLink)}
|
|
113
|
+
logo-link=${ifDefined(this.logoLink)}
|
|
114
|
+
search-placeholder=${this.searchPlaceholder}
|
|
115
|
+
search-value=${this.searchValue}
|
|
116
|
+
mobile-breakpoint=${this.mobileBreakpoint}
|
|
117
|
+
tablet-breakpoint=${this.tabletBreakpoint}
|
|
118
|
+
opened-menu=${this.openedMenu}
|
|
119
|
+
log-out-button-placement=${this.logOutButtonPlacement}
|
|
120
|
+
position=${this.position}
|
|
121
|
+
scroll-behavior=${this.scrollBehavior}
|
|
122
|
+
.hideLogo=${this.hideLogo}
|
|
123
|
+
.compact=${this.compact}
|
|
124
|
+
.showSearch=${this.showSearch}
|
|
125
|
+
.canChangeRepresentation=${this.canChangeRepresentation}
|
|
126
|
+
.hasLogOut=${this.hasLogOut}
|
|
127
|
+
.user=${this.user}
|
|
128
|
+
.userMenu=${this.effectiveUserMenu}
|
|
129
|
+
.representing=${this.representing}
|
|
130
|
+
>
|
|
131
|
+
<div class="pkt-contents" ${ref(this.defaultSlot)}></div>
|
|
132
|
+
</pkt-header-service>
|
|
133
|
+
`
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare global {
|
|
138
|
+
interface HTMLElementTagNameMap {
|
|
139
|
+
'pkt-header': PktHeader
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { PktHeader } from './header'
|
|
2
|
+
export { PktHeaderService } from './header-service'
|
|
3
|
+
export { PktHeaderUserMenu } from './header-user-menu'
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
User,
|
|
7
|
+
Representing,
|
|
8
|
+
UserMenuItem,
|
|
9
|
+
TInternalMenuItem,
|
|
10
|
+
TPktHeaderMenu,
|
|
11
|
+
TLogOutButtonPlacement,
|
|
12
|
+
IPktHeader,
|
|
13
|
+
} from './types'
|
|
14
|
+
|
|
15
|
+
export { formatLastLoggedIn } from './header-utils'
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { PktIconName } from '@oslokommune/punkt-assets/dist/icons/icon'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Booleanish type that accepts boolean or string "true"/"false"
|
|
5
|
+
* This allows attributes to be set as strings in HTML
|
|
6
|
+
*/
|
|
7
|
+
export type Booleanish = boolean | 'true' | 'false'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Converter for booleanish attributes
|
|
11
|
+
* Converts string "true"/"false" to boolean values
|
|
12
|
+
*/
|
|
13
|
+
export const booleanishConverter = {
|
|
14
|
+
fromAttribute(value: string | boolean | null): boolean {
|
|
15
|
+
if (value === null || value === undefined) return false
|
|
16
|
+
if (value === '' || value === 'true' || value === true) return true
|
|
17
|
+
if (value === 'false' || value === false) return false
|
|
18
|
+
return Boolean(value)
|
|
19
|
+
},
|
|
20
|
+
toAttribute(value: boolean): string | null {
|
|
21
|
+
return value ? 'true' : 'false'
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* User object containing information about the logged-in user
|
|
27
|
+
*/
|
|
28
|
+
export interface User {
|
|
29
|
+
/** Full name of the user */
|
|
30
|
+
name: string
|
|
31
|
+
/** Short name or initials (deprecated) */
|
|
32
|
+
shortname?: string
|
|
33
|
+
/** Last login timestamp (ISO string or Date) */
|
|
34
|
+
lastLoggedIn?: Date | string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Representation object containing information about the organization/entity being represented
|
|
39
|
+
*/
|
|
40
|
+
export interface Representing {
|
|
41
|
+
/** Name of the organization or entity */
|
|
42
|
+
name: string
|
|
43
|
+
/** Short name or initials (deprecated) */
|
|
44
|
+
shortname?: string
|
|
45
|
+
/** Organization number */
|
|
46
|
+
orgNumber?: string | number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Menu item in the user menu
|
|
51
|
+
*/
|
|
52
|
+
export interface UserMenuItem {
|
|
53
|
+
/** Icon name to display */
|
|
54
|
+
iconName?: PktIconName
|
|
55
|
+
/** Text for the menu item */
|
|
56
|
+
title: string
|
|
57
|
+
/** Link URL or click handler function */
|
|
58
|
+
target: string | (() => void)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Internal type for rendering links/buttons (supports both href and onClick)
|
|
63
|
+
*/
|
|
64
|
+
interface InternalMenuItemBase {
|
|
65
|
+
title: string
|
|
66
|
+
iconName?: PktIconName
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface InternalMenuLink extends InternalMenuItemBase {
|
|
70
|
+
href: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface InternalMenuButton extends InternalMenuItemBase {
|
|
74
|
+
onClick: () => void
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type TInternalMenuItem = InternalMenuLink | InternalMenuButton
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Helper to convert UserMenuItem (with target) to internal format
|
|
81
|
+
*/
|
|
82
|
+
export const convertUserMenuItem = (item: UserMenuItem): TInternalMenuItem => {
|
|
83
|
+
if (typeof item.target === 'string') {
|
|
84
|
+
return { title: item.title, iconName: item.iconName, href: item.target }
|
|
85
|
+
}
|
|
86
|
+
return { title: item.title, iconName: item.iconName, onClick: item.target }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Type for which menu is currently open
|
|
91
|
+
*/
|
|
92
|
+
export type TPktHeaderMenu = 'none' | 'slot' | 'search' | 'user'
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Type for logout button placement
|
|
96
|
+
*/
|
|
97
|
+
export type TLogOutButtonPlacement = 'userMenu' | 'header' | 'both' | 'none'
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Position options for header
|
|
101
|
+
*/
|
|
102
|
+
export type THeaderPosition = 'fixed' | 'relative'
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Scroll behavior options for header
|
|
106
|
+
*/
|
|
107
|
+
export type THeaderScrollBehavior = 'hide' | 'none'
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Interface for the Header component props
|
|
111
|
+
*/
|
|
112
|
+
export interface IPktHeader {
|
|
113
|
+
/** Hide the Oslo logo */
|
|
114
|
+
hideLogo?: Booleanish
|
|
115
|
+
/** Logo link URL */
|
|
116
|
+
logoLink?: string
|
|
117
|
+
/** Service name displayed in the header */
|
|
118
|
+
serviceName?: string
|
|
119
|
+
/** Service link URL */
|
|
120
|
+
serviceLink?: string
|
|
121
|
+
/** Use compact header height */
|
|
122
|
+
compact?: Booleanish
|
|
123
|
+
/** Header position. 'fixed' fixes to top of viewport, 'relative' follows document flow. Default: 'fixed' */
|
|
124
|
+
position?: THeaderPosition
|
|
125
|
+
/** Scroll behavior. 'hide' hides header on scroll down, 'none' keeps it visible. Default: 'hide' */
|
|
126
|
+
scrollBehavior?: THeaderScrollBehavior
|
|
127
|
+
/** User object for logged-in user */
|
|
128
|
+
user?: User
|
|
129
|
+
/** User menu items */
|
|
130
|
+
userMenu?: UserMenuItem[]
|
|
131
|
+
/** Representation object */
|
|
132
|
+
representing?: Representing
|
|
133
|
+
/** Allow user to change representation */
|
|
134
|
+
canChangeRepresentation?: Booleanish
|
|
135
|
+
/** Logout button placement */
|
|
136
|
+
logOutButtonPlacement?: TLogOutButtonPlacement
|
|
137
|
+
/** Whether there's a logout handler attached (required for logout button to show) */
|
|
138
|
+
hasLogOut?: Booleanish
|
|
139
|
+
/** Show search field */
|
|
140
|
+
showSearch?: Booleanish
|
|
141
|
+
/** Search field placeholder */
|
|
142
|
+
searchPlaceholder?: string
|
|
143
|
+
/** Controlled search value */
|
|
144
|
+
searchValue?: string
|
|
145
|
+
/** Custom breakpoint for responsive behavior in pixels. Default: 1024 */
|
|
146
|
+
mobileBreakpoint?: number
|
|
147
|
+
/** Custom breakpoint for tablet responsive behavior in pixels. Default: 1280 */
|
|
148
|
+
tabletBreakpoint?: number
|
|
149
|
+
/** Which menu is initially open */
|
|
150
|
+
openedMenu?: TPktHeaderMenu
|
|
151
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { PktCheckbox } from '@/components/checkbox'
|
|
|
11
11
|
export { PktComponent } from '../base-elements/component-template.js'
|
|
12
12
|
export { PktDateTags } from '@/components/datepicker/date-tags.js'
|
|
13
13
|
export { PktDatepicker } from '@/components/datepicker/datepicker.js'
|
|
14
|
+
export { PktHeader, PktHeaderService, PktHeaderUserMenu } from '@/components/header'
|
|
14
15
|
export { PktHelptext } from '@/components/helptext'
|
|
15
16
|
export { PktHeading } from '@/components/heading'
|
|
16
17
|
export { PktIcon } from '@/components/icon'
|
|
@@ -44,6 +45,15 @@ export type {
|
|
|
44
45
|
TPktButtonType,
|
|
45
46
|
} from '@/components/button'
|
|
46
47
|
|
|
48
|
+
export type {
|
|
49
|
+
User as IPktHeaderUser,
|
|
50
|
+
Representing as IPktHeaderRepresenting,
|
|
51
|
+
UserMenuItem as IPktHeaderUserMenuItem,
|
|
52
|
+
TPktHeaderMenu,
|
|
53
|
+
TLogOutButtonPlacement as TPktHeaderLogOutButtonPlacement,
|
|
54
|
+
IPktHeader,
|
|
55
|
+
} from '@/components/header'
|
|
56
|
+
|
|
47
57
|
export type {
|
|
48
58
|
IPktProgressbar,
|
|
49
59
|
TProgressbarRole,
|