@oslokommune/punkt-react 16.26.5 → 17.0.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 +93 -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/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 +3188 -2729
- 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/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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* footer.ts
|
|
3
|
+
*
|
|
4
|
+
* Framework-agnostic prop types for the reworked footer components
|
|
5
|
+
* (`PktFooter` / `pkt-footer` and `PktFooterSimple` / `pkt-footer-simple`).
|
|
6
|
+
*
|
|
7
|
+
* Both components render an optional grey "secondary" band with the
|
|
8
|
+
* consumer's custom content, stacked on top of the global (blue) footer
|
|
9
|
+
* whose content comes from the same CDN payload as the global header —
|
|
10
|
+
* see `header-footer.ts`.
|
|
11
|
+
*/
|
|
12
|
+
import type { THeaderFooterApi, THeaderMenuLocale } from './header-footer';
|
|
13
|
+
/** Custom link in the secondary (grey) footer. */
|
|
14
|
+
export interface IPktFooterLink {
|
|
15
|
+
href: string;
|
|
16
|
+
text: string;
|
|
17
|
+
/**
|
|
18
|
+
* Mark the link as external: the link's top domain is shown in
|
|
19
|
+
* parentheses after the text ("Ledige stillinger (webcruiter.com)"),
|
|
20
|
+
* per Punkt's link-labelling guidance.
|
|
21
|
+
*/
|
|
22
|
+
external?: boolean;
|
|
23
|
+
openInNewTab?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Column of custom content in the secondary footer (1–3 per footer). */
|
|
26
|
+
export interface IPktFooterColumn {
|
|
27
|
+
title: string;
|
|
28
|
+
/** Optional free-text paragraph shown above the links. */
|
|
29
|
+
text?: string;
|
|
30
|
+
links?: IPktFooterLink[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated The social-media row now comes from the global footer's
|
|
34
|
+
* payload. Only rendered when explicitly supplied.
|
|
35
|
+
*/
|
|
36
|
+
export interface IPktFooterSocialLink {
|
|
37
|
+
href: string;
|
|
38
|
+
iconName?: string;
|
|
39
|
+
language?: string;
|
|
40
|
+
openInNewTab?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** Props shared by `PktFooter` and `PktFooterSimple` in both frameworks. */
|
|
43
|
+
export interface IPktFooterBase<IconType = string> {
|
|
44
|
+
/** Pre-fetched header/footer payload. If supplied, the component skips its own fetch. */
|
|
45
|
+
data?: THeaderFooterApi<IconType>;
|
|
46
|
+
/** Endpoint to fetch the header/footer payload from. */
|
|
47
|
+
dataUrl?: string;
|
|
48
|
+
/** Locale key to select from the payload. Defaults to `nb-NO`. */
|
|
49
|
+
locale?: THeaderMenuLocale;
|
|
50
|
+
/** Skip the global (blue) footer entirely, e.g. when CSP blocks the CDN. Default: false */
|
|
51
|
+
skipGlobal?: boolean;
|
|
52
|
+
/** Open custom links in a new tab. Applies to custom content only. Default: false */
|
|
53
|
+
openLinksInNewTab?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Replace the payload's cookie-settings link (`#cb-trigger`) with
|
|
56
|
+
* Punkt's own consent dialog. When unset the cookie-settings link is
|
|
57
|
+
* hidden. Default: false
|
|
58
|
+
*/
|
|
59
|
+
includeConsent?: boolean;
|
|
60
|
+
hotjarId?: string | null;
|
|
61
|
+
googleAnalyticsId?: string | null;
|
|
62
|
+
devMode?: boolean;
|
|
63
|
+
cookieDomain?: string | null;
|
|
64
|
+
cookieSecure?: string | null;
|
|
65
|
+
cookieExpiryDays?: string | null;
|
|
66
|
+
}
|
|
67
|
+
export interface IPktFooterProps<IconType = string> extends IPktFooterBase<IconType> {
|
|
68
|
+
/** Custom content columns for the secondary (grey) footer. */
|
|
69
|
+
columns?: IPktFooterColumn[];
|
|
70
|
+
/** @deprecated Use `columns`. Mapped to the first entry of `columns`. */
|
|
71
|
+
columnOne?: IPktFooterColumn;
|
|
72
|
+
/** @deprecated Use `columns`. Mapped to the second entry of `columns`. */
|
|
73
|
+
columnTwo?: IPktFooterColumn;
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated The privacy link now comes from the global footer's payload.
|
|
76
|
+
* Only rendered (in the secondary footer) when explicitly set to a
|
|
77
|
+
* non-default value.
|
|
78
|
+
*/
|
|
79
|
+
personvernOgInfoLink?: string;
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated The accessibility link now comes from the global footer's
|
|
82
|
+
* payload. Only rendered (in the secondary footer) when explicitly set
|
|
83
|
+
* to a non-default value.
|
|
84
|
+
*/
|
|
85
|
+
tilgjengelighetLink?: string;
|
|
86
|
+
/** @deprecated The social-media row now comes from the global footer's payload. */
|
|
87
|
+
socialLinks?: IPktFooterSocialLink[];
|
|
88
|
+
}
|
|
89
|
+
export interface IPktFooterSimpleProps<IconType = string> extends IPktFooterBase<IconType> {
|
|
90
|
+
/** Custom links shown as a compact row in the secondary (grey) footer. */
|
|
91
|
+
links?: IPktFooterLink[];
|
|
92
|
+
}
|
|
93
|
+
/** Historical defaults for the deprecated link props — used to detect explicit overrides. */
|
|
94
|
+
export declare const DEFAULT_FOOTER_PERSONVERN_URL = "https://www.oslo.kommune.no/personvern-og-informasjonskapsler/";
|
|
95
|
+
export declare const DEFAULT_FOOTER_TILGJENGELIGHET_URL = "https://www.oslo.kommune.no/tilgjengelighet/";
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* header-
|
|
2
|
+
* header-footer.ts
|
|
3
3
|
*
|
|
4
|
-
* Type definitions for the global header mega menu,
|
|
5
|
-
*
|
|
4
|
+
* Type definitions for the global header/footer payload (mega menu,
|
|
5
|
+
* footer, search and contact), shared between @oslokommune/punkt-elements
|
|
6
|
+
* and @oslokommune/punkt-react.
|
|
6
7
|
*
|
|
7
8
|
* The shape mirrors the live API response from
|
|
8
9
|
* https://cdn.web.oslo.kommune.no/header-footer/header-footer.json
|
|
9
|
-
* which the
|
|
10
|
+
* which the "live" oslo.kommune.no header consumes.
|
|
10
11
|
*/
|
|
11
12
|
/**
|
|
12
13
|
* Locale keys present in the live payload. Typed as a string union with a
|
|
13
14
|
* `string & {}` escape hatch so additional locales can be added without
|
|
14
15
|
* a type-level breaking change.
|
|
15
16
|
*/
|
|
17
|
+
import type { Representing, TLogOutButtonPlacement, THeaderPosition, THeaderScrollBehavior, User, UserMenuItem } from './header';
|
|
16
18
|
export type THeaderMenuLocale = 'nb-NO' | 'en-GB' | (string & {});
|
|
17
19
|
/** Plain link without an icon. */
|
|
18
20
|
export interface IHeaderMenuLink {
|
|
@@ -104,5 +106,50 @@ export interface IPktHeaderMenu<IconType = string> {
|
|
|
104
106
|
/** Pixel breakpoint below which sections collapse into accordions. */
|
|
105
107
|
mobileBreakpoint?: number;
|
|
106
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Framework-agnostic props for the global (kommune-wide) header
|
|
111
|
+
* (`pkt-header-global` / `PktHeaderGlobal`). Elements narrows the
|
|
112
|
+
* boolean fields to `Booleanish` and React adds callback/className
|
|
113
|
+
* props on top — see each package's `header/types.ts`.
|
|
114
|
+
*/
|
|
115
|
+
export interface IPktHeaderGlobal<IconType = string> {
|
|
116
|
+
/** Endpoint to fetch the header/footer payload from. */
|
|
117
|
+
dataUrl?: string;
|
|
118
|
+
/** Pre-fetched payload. If supplied, the component (and its menu) skips its own fetch. */
|
|
119
|
+
data?: THeaderFooterApi<IconType>;
|
|
120
|
+
/** Locale key to select from the payload. Defaults to `nb-NO`. */
|
|
121
|
+
locale?: THeaderMenuLocale;
|
|
122
|
+
/** Logo link URL. When omitted the logo is rendered without a link. */
|
|
123
|
+
logoLink?: string;
|
|
124
|
+
/** Override the CDN path the logo assets load from. Can also be set
|
|
125
|
+
* globally via `window.pktLogoPath`. */
|
|
126
|
+
logoPath?: string;
|
|
127
|
+
/** Show the search field. Default: true */
|
|
128
|
+
showSearch?: boolean;
|
|
129
|
+
/** Placeholder for the search field. Falls back to the payload's search placeholder, then 'Søk'. */
|
|
130
|
+
searchPlaceholder?: string;
|
|
131
|
+
/** Show the contact link (from the payload's `header.contact`). Default: true */
|
|
132
|
+
showContact?: boolean;
|
|
133
|
+
/** User object for the logged-in user. When set, the user menu is shown. */
|
|
134
|
+
user?: User;
|
|
135
|
+
/** User menu items. */
|
|
136
|
+
userMenu?: UserMenuItem<IconType>[];
|
|
137
|
+
/** Representation object. */
|
|
138
|
+
representing?: Representing;
|
|
139
|
+
/** Allow the user to change representation. */
|
|
140
|
+
canChangeRepresentation?: boolean;
|
|
141
|
+
/** Logout button placement. */
|
|
142
|
+
logOutButtonPlacement?: TLogOutButtonPlacement;
|
|
143
|
+
/** Header position. Default: 'fixed' */
|
|
144
|
+
position?: THeaderPosition;
|
|
145
|
+
/** Scroll behavior. Default: 'hide' */
|
|
146
|
+
scrollBehavior?: THeaderScrollBehavior;
|
|
147
|
+
/** Width below which the header switches to mobile layout. Default: 768 */
|
|
148
|
+
mobileBreakpoint?: number;
|
|
149
|
+
/** Width below which the header switches to tablet layout. Default: 1024 */
|
|
150
|
+
tabletBreakpoint?: number;
|
|
151
|
+
}
|
|
107
152
|
/** Default URL of the live header/footer endpoint. */
|
|
108
153
|
export declare const DEFAULT_HEADER_FOOTER_URL = "https://cdn.web.oslo.kommune.no/header-footer/header-footer.json";
|
|
154
|
+
/** Default logo link for both header variants (unless overridden or hidden). */
|
|
155
|
+
export declare const DEFAULT_HEADER_LOGO_LINK = "https://www.oslo.kommune.no";
|
|
@@ -81,7 +81,7 @@ export type TLogOutButtonPlacement = 'userMenu' | 'header' | 'both' | 'none';
|
|
|
81
81
|
/**
|
|
82
82
|
* Position options for the header
|
|
83
83
|
*/
|
|
84
|
-
export type THeaderPosition = 'fixed' | 'relative';
|
|
84
|
+
export type THeaderPosition = 'fixed' | 'sticky' | 'relative';
|
|
85
85
|
/**
|
|
86
86
|
* Scroll behavior options for the header
|
|
87
87
|
*/
|
|
@@ -4,12 +4,13 @@ export type { TAriaBoolean, TAriaBooleanMixed, TAriaCurrent, TAriaHasPopup, TAri
|
|
|
4
4
|
export type { THeadingLevel, THeadingSize, THeadingWeight } from './heading';
|
|
5
5
|
export type { User, Representing, UserMenuItem, InternalMenuItemBase, InternalMenuLink, InternalMenuButton, TInternalMenuItem, TSlotMenuVariant, THeaderMenu, TLogOutButtonPlacement, THeaderPosition, THeaderScrollBehavior, } from './header';
|
|
6
6
|
export { convertUserMenuItem } from './header';
|
|
7
|
-
export type { THeaderMenuLocale, IHeaderMenuLink, IHeaderMenuIconLink, IHeaderMenuButton, IHeaderMenuServices, IHeaderMenuSection, IMegaMenu, IHeaderFooter, IHeaderFooterLocaleData, THeaderFooterApi, IPktHeaderMenu, } from './header-
|
|
8
|
-
export { DEFAULT_HEADER_FOOTER_URL } from './header-
|
|
7
|
+
export type { THeaderMenuLocale, IHeaderMenuLink, IHeaderMenuIconLink, IHeaderMenuButton, IHeaderMenuServices, IHeaderMenuSection, IMegaMenu, IHeaderFooter, IHeaderFooterLocaleData, THeaderFooterApi, IPktHeaderMenu, IPktHeaderGlobal, } from './header-footer';
|
|
8
|
+
export { DEFAULT_HEADER_FOOTER_URL, DEFAULT_HEADER_LOGO_LINK } from './header-footer';
|
|
9
|
+
export type { IPktFooterLink, IPktFooterColumn, IPktFooterSocialLink, IPktFooterBase, IPktFooterProps, IPktFooterSimpleProps, } from './footer';
|
|
10
|
+
export { DEFAULT_FOOTER_PERSONVERN_URL, DEFAULT_FOOTER_TILGJENGELIGHET_URL } from './footer';
|
|
9
11
|
export type { TLayout, TVerticalPosition, THorizontalPosition, TPosition, TPositionWithCenter, } from './layout';
|
|
10
12
|
export type { THTMLButtonType, TSelectionMode } from './form';
|
|
11
|
-
export type {
|
|
12
|
-
export type { TSize3, TSize5, TSize7 } from './size';
|
|
13
|
+
export type { TPktInputSize, TSize3, TSize5, TSize7 } from './size';
|
|
13
14
|
export type { TAlertSkin, TMessageboxSkin, TAccordionSkin, TCardSkin, TTagSkin } from './skin';
|
|
14
15
|
export type { IDateConstraints, ICalendarGridDimensions, IGridCellPosition, TDateRangeMap, ISelectionState, ISelectionOptions, } from './calendar';
|
|
15
16
|
export type { IDatepickerStrings } from './datepicker';
|
|
@@ -18,6 +19,7 @@ export type { ITimepickerStrings } from './timepicker';
|
|
|
18
19
|
export { defaultTimepickerStrings } from './timepicker';
|
|
19
20
|
export type { TProgressbarRole, TProgressbarSkin, TProgressbarStatusPlacement, TProgressbarStatusType, TProgressbarTitlePosition, } from './progressbar';
|
|
20
21
|
export type { IPktComboboxOption, TPktComboboxTagSkin, TPktComboboxDisplayValue, TPktComboboxTagPlacement, } from './combobox';
|
|
22
|
+
export type { IPktSelectOption, TSelectOption } from './select';
|
|
21
23
|
export type { TPktSearchInputAppearance, TPktSearchInputMethod, IPktSearchInputSuggestion, IPktSearchInput, } from './searchinput';
|
|
22
24
|
export type { TUploadStrategy as TFileUploadStrategy, TFileUploadItemRenderer, TFilesChangedReason, TFileValidator, TTransferProgress, IFileItem, IFileTransfer, IFileValidateDetail, ITransferCancelledDetail, IFilesChangedDetail, IFileUploadStrings, } from './fileupload';
|
|
23
25
|
export { defaultFileUploadStrings } from './fileupload';
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* stay aligned. React extends suggestions with `ReactNode` and optional
|
|
6
6
|
* `onClick` in the component layer — see `@oslokommune/punkt-react`.
|
|
7
7
|
*/
|
|
8
|
+
import type { TPktInputSize } from './size';
|
|
8
9
|
export type TPktSearchInputAppearance = 'local' | 'local-with-button' | 'global';
|
|
9
10
|
export type TPktSearchInputMethod = 'get' | 'post' | 'dialog';
|
|
10
11
|
/**
|
|
@@ -28,6 +29,7 @@ export interface IPktSearchInputSuggestion {
|
|
|
28
29
|
*/
|
|
29
30
|
export interface IPktSearchInput {
|
|
30
31
|
appearance?: TPktSearchInputAppearance;
|
|
32
|
+
inputSize?: TPktInputSize;
|
|
31
33
|
disabled?: boolean;
|
|
32
34
|
fullwidth?: boolean;
|
|
33
35
|
id: string;
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Lives in shared-types so both `@oslokommune/punkt-elements` and
|
|
4
4
|
* `@oslokommune/punkt-react` can re-export the same canonical type.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
6
|
+
export interface IPktSelectOption {
|
|
7
7
|
value: string;
|
|
8
8
|
label: string;
|
|
9
9
|
selected?: boolean;
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
hidden?: boolean;
|
|
12
|
-
}
|
|
12
|
+
}
|
|
13
|
+
export type TSelectOption = IPktSelectOption;
|
|
@@ -16,3 +16,7 @@ export type TSize5 = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';
|
|
|
16
16
|
* Full 7-level size scale
|
|
17
17
|
*/
|
|
18
18
|
export type TSize7 = 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';
|
|
19
|
+
/**
|
|
20
|
+
* Sizes used in our input components
|
|
21
|
+
*/
|
|
22
|
+
export type TPktInputSize = 'xsmall' | 'small' | 'medium';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TPktInputSize } from '../shared-types/size';
|
|
1
2
|
/**
|
|
2
3
|
* Shared datepicker utility functions used by both Elements and React packages.
|
|
3
4
|
* Only framework-agnostic functions belong here.
|
|
@@ -23,11 +24,11 @@ export declare const filterDates: (dates: string[], min?: string | null, max?: s
|
|
|
23
24
|
/**
|
|
24
25
|
* Generates input CSS classes for datepicker.
|
|
25
26
|
*/
|
|
26
|
-
export declare const getDatepickerInputClasses: (fullwidth: boolean, showRangeLabels: boolean, multiple: boolean, range: boolean, readonly?: boolean, inputType?: string) => Record<string, boolean>;
|
|
27
|
+
export declare const getDatepickerInputClasses: (fullwidth: boolean, showRangeLabels: boolean, multiple: boolean, range: boolean, readonly?: boolean, inputType?: string, inputSize?: TPktInputSize) => Record<string, boolean>;
|
|
27
28
|
/**
|
|
28
29
|
* Generates button CSS classes for calendar toggle button.
|
|
29
30
|
*/
|
|
30
|
-
export declare const getDatepickerButtonClasses: () => Record<string, boolean>;
|
|
31
|
+
export declare const getDatepickerButtonClasses: (inputSize?: TPktInputSize) => Record<string, boolean>;
|
|
31
32
|
/**
|
|
32
33
|
* Generates range label CSS classes.
|
|
33
34
|
*/
|
|
@@ -62,8 +63,8 @@ declare const _default: {
|
|
|
62
63
|
validateRangeOrder: (values: string[]) => boolean;
|
|
63
64
|
sortDates: (dates: string[]) => string[];
|
|
64
65
|
filterDates: (dates: string[], min?: string | null, max?: string | null, excludedDates?: string[], excludedWeekdays?: string[]) => string[];
|
|
65
|
-
getDatepickerInputClasses: (fullwidth: boolean, showRangeLabels: boolean, multiple: boolean, range: boolean, readonly?: boolean, inputType?: string) => Record<string, boolean>;
|
|
66
|
-
getDatepickerButtonClasses: () => Record<string, boolean>;
|
|
66
|
+
getDatepickerInputClasses: (fullwidth: boolean, showRangeLabels: boolean, multiple: boolean, range: boolean, readonly?: boolean, inputType?: string, inputSize?: TPktInputSize) => Record<string, boolean>;
|
|
67
|
+
getDatepickerButtonClasses: (inputSize?: TPktInputSize) => Record<string, boolean>;
|
|
67
68
|
getRangeLabelClasses: (showRangeLabels: boolean) => Record<string, boolean>;
|
|
68
69
|
processDateSelection: (detail: string | string[], multiple: boolean, range: boolean) => string;
|
|
69
70
|
handleCalendarPosition: (popup: HTMLElement | null, input: HTMLElement | null, hasCounter?: boolean) => void;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Re-export of the generic focus-trap helpers, kept here for backwards
|
|
3
|
+
* compatibility with existing `shared-utils/fileupload` imports. The canonical
|
|
4
|
+
* implementation now lives in `shared-utils/focus-trap`.
|
|
4
5
|
*/
|
|
5
|
-
export
|
|
6
|
-
/**
|
|
7
|
-
* Keyboard handler that wraps Tab / Shift-Tab focus around the first and last
|
|
8
|
-
* focusable elements inside a container. Returns `true` if the event was handled.
|
|
9
|
-
*
|
|
10
|
-
* Call from a `keydown` listener on the modal root.
|
|
11
|
-
*/
|
|
12
|
-
export declare function trapTabInside(event: KeyboardEvent, container: HTMLElement | null | undefined): boolean;
|
|
6
|
+
export { getFocusableElements, trapTabInside } from '../focus-trap';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a list of focusable elements inside a container (same selector used by
|
|
3
|
+
* the fileupload preview modals and the global header mega menu). Hidden
|
|
4
|
+
* elements are filtered out.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getFocusableElements(container: HTMLElement | null | undefined): HTMLElement[];
|
|
7
|
+
/**
|
|
8
|
+
* Like {@link getFocusableElements}, but descends open shadow roots, treats a
|
|
9
|
+
* native `<summary>` as focusable, and skips content hidden inside a collapsed
|
|
10
|
+
* `<details>`. Returned in flattened tree order. Use this when the container may
|
|
11
|
+
* hold web components or `<details>` accordions (e.g. the header mega menu).
|
|
12
|
+
*/
|
|
13
|
+
export declare function getFocusableElementsDeep(container: HTMLElement | null | undefined): HTMLElement[];
|
|
14
|
+
/** Resolve the genuinely focused element, piercing nested open shadow roots. */
|
|
15
|
+
export declare function getDeepActiveElement(doc?: Document): HTMLElement | null;
|
|
16
|
+
/**
|
|
17
|
+
* Keyboard handler that wraps Tab / Shift-Tab focus around the first and last
|
|
18
|
+
* focusable elements inside a container. Returns `true` if the event was handled.
|
|
19
|
+
*
|
|
20
|
+
* Call from a `keydown` listener on the modal root.
|
|
21
|
+
*/
|
|
22
|
+
export declare function trapTabInside(event: KeyboardEvent, container: HTMLElement | null | undefined): boolean;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { THeaderFooterApi } from '../../shared-types/header-
|
|
1
|
+
import type { THeaderFooterApi } from '../../shared-types/header-footer';
|
|
2
2
|
/**
|
|
3
3
|
* Example header/footer payload used by the dev/demo pages in both
|
|
4
|
-
* Punkt Elements and Punkt React.
|
|
5
|
-
* payload
|
|
6
|
-
*
|
|
4
|
+
* Punkt Elements and Punkt React. Verbatim copy of the live Oslo
|
|
5
|
+
* kommune payload from
|
|
6
|
+
* https://cdn.web.oslo.kommune.no/header-footer/header-footer.json
|
|
7
|
+
* (hentet 2026-07-06).
|
|
7
8
|
*
|
|
8
9
|
* Tests use focused per-test fixtures with smaller shapes — this
|
|
9
10
|
* dataset is for visual demos only.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External-link labelling for the global footer.
|
|
3
|
+
*
|
|
4
|
+
* Punkt's link-labelling guidance (punkt.oslo.kommune.no/god-praksis/merking-av-lenker/)
|
|
5
|
+
* prefers spelling out the destination domain in parentheses after the link
|
|
6
|
+
* text — "Ledige stillinger (webcruiter.com)" — over an external-link icon.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Return the top domain of an absolute http(s) URL, or `undefined` for
|
|
10
|
+
* relative URLs, fragments, and non-web schemes (`mailto:`, `tel:`).
|
|
11
|
+
* Unlike `getExternalDomainLabel` this does no internal-host check — use
|
|
12
|
+
* it when the consumer has explicitly flagged the link as external.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getLinkDomainLabel(url: string | undefined): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Return the top domain to display after an external link's text, or
|
|
17
|
+
* `undefined` when the link needs no label:
|
|
18
|
+
*
|
|
19
|
+
* - relative URLs and fragments (`/jobb`, `#cb-trigger`) — same site
|
|
20
|
+
* - non-http(s) schemes (`mailto:`, `tel:`) — not web navigation
|
|
21
|
+
* - hosts on `oslo.kommune.no` (or a subdomain) — internal
|
|
22
|
+
*
|
|
23
|
+
* `internalHost` can widen what counts as internal, e.g. for consumers
|
|
24
|
+
* hosting the payload themselves.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getExternalDomainLabel(url: string | undefined, internalHost?: string): string | undefined;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { DEFAULT_HEADER_FOOTER_URL, type THeaderFooterApi } from '../../shared-types/header-
|
|
1
|
+
import { DEFAULT_HEADER_FOOTER_URL, type THeaderFooterApi } from '../../shared-types/header-footer';
|
|
2
2
|
export { DEFAULT_HEADER_FOOTER_URL };
|
|
3
|
+
/** Clear the shared payload cache. Intended for tests. */
|
|
4
|
+
export declare function resetHeaderFooterDataCache(): void;
|
|
3
5
|
/**
|
|
4
6
|
* Fetch and parse the Oslo kommune header/footer JSON payload.
|
|
5
7
|
*
|
|
@@ -7,6 +9,8 @@ export { DEFAULT_HEADER_FOOTER_URL };
|
|
|
7
9
|
* for surfacing or hiding errors — the Lit element, for example, hides
|
|
8
10
|
* the menu and dispatches a `data-error` event.
|
|
9
11
|
*
|
|
10
|
-
* `signal` lets callers abort
|
|
12
|
+
* `signal` lets callers abort their own wait when the component
|
|
13
|
+
* disconnects; the underlying request is shared and keeps running for
|
|
14
|
+
* any other component awaiting the same URL.
|
|
11
15
|
*/
|
|
12
16
|
export declare function fetchHeaderFooterData<IconType = string>(url?: string, signal?: AbortSignal): Promise<THeaderFooterApi<IconType>>;
|
|
@@ -7,5 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { ICON_MAP_ODS_TO_PUNKT, mapOdsIcon, deriveSocialIcon } from './icon-map';
|
|
9
9
|
export { selectLocaleData, selectMegamenu } from './select-megamenu';
|
|
10
|
-
export {
|
|
10
|
+
export { getExternalDomainLabel, getLinkDomainLabel } from './external-domain';
|
|
11
|
+
export { DEFAULT_HEADER_FOOTER_URL, fetchHeaderFooterData, resetHeaderFooterDataCache, } from './fetch-header-footer-data';
|
|
11
12
|
export { EXAMPLE_HEADER_FOOTER_DATA } from './example-data';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IHeaderFooterLocaleData, IMegaMenu, THeaderFooterApi, THeaderMenuLocale } from '../../shared-types/header-
|
|
1
|
+
import type { IHeaderFooterLocaleData, IMegaMenu, THeaderFooterApi, THeaderMenuLocale } from '../../shared-types/header-footer';
|
|
2
2
|
/**
|
|
3
3
|
* Pick the locale slice from a header/footer payload. Falls back to
|
|
4
4
|
* `nb-NO` when the requested locale is missing — the same fallback the
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared body scroll-lock used by every overlay in the design system (modal,
|
|
3
|
+
* service/global header menus, search and user dropdowns).
|
|
4
|
+
*
|
|
5
|
+
* Strategy: an iOS-safe `position: fixed` lock — `overflow: hidden` alone is
|
|
6
|
+
* ignored by iOS Safari. The current scroll position is saved, the body is
|
|
7
|
+
* pinned, scrollbar width is reserved to avoid a layout shift, and the scroll
|
|
8
|
+
* position is restored on release.
|
|
9
|
+
*
|
|
10
|
+
* Reference-counted so several open overlays don't unlock the page when one of
|
|
11
|
+
* them closes. Each caller holds a token (see {@link createScrollLockToken}) so
|
|
12
|
+
* repeated lock/unlock calls from the same source are idempotent — only the
|
|
13
|
+
* first lock and the last unlock touch the DOM.
|
|
14
|
+
*/
|
|
15
|
+
export interface ScrollLockToken {
|
|
16
|
+
locked: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const createScrollLockToken: () => ScrollLockToken;
|
|
19
|
+
/**
|
|
20
|
+
* Lock or unlock body scroll for a single source. Pass a token created with
|
|
21
|
+
* {@link createScrollLockToken} so the call is idempotent — locking an already
|
|
22
|
+
* locked token (or unlocking an unlocked one) is a no-op.
|
|
23
|
+
*/
|
|
24
|
+
export declare const setScrollLock: (token: ScrollLockToken, shouldLock: boolean) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.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",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@eslint/compat": "^2.1.0",
|
|
45
45
|
"@eslint/eslintrc": "^3.3.5",
|
|
46
46
|
"@eslint/js": "^10.0.1",
|
|
47
|
-
"@oslokommune/punkt-assets": "^
|
|
48
|
-
"@oslokommune/punkt-css": "^
|
|
47
|
+
"@oslokommune/punkt-assets": "^17.0.0",
|
|
48
|
+
"@oslokommune/punkt-css": "^17.0.1",
|
|
49
49
|
"@testing-library/jest-dom": "^6.9.1",
|
|
50
50
|
"@testing-library/react": "^16.3.2",
|
|
51
51
|
"@testing-library/user-event": "^14.6.1",
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
102
102
|
},
|
|
103
103
|
"license": "MIT",
|
|
104
|
-
"gitHead": "
|
|
104
|
+
"gitHead": "8f7e45b1c5c353908cb5f4232d181dce53f3147f"
|
|
105
105
|
}
|
|
@@ -76,6 +76,57 @@ describe('PktAccordion', () => {
|
|
|
76
76
|
expect(ref.current?.tagName).toBe('DIV')
|
|
77
77
|
expect(ref.current).toHaveClass('pkt-accordion')
|
|
78
78
|
})
|
|
79
|
+
|
|
80
|
+
test('propagates plus-minus skin to items', () => {
|
|
81
|
+
const { container } = render(
|
|
82
|
+
<PktAccordion skin="plus-minus">
|
|
83
|
+
<PktAccordionItem title="Title" id="item1">
|
|
84
|
+
Content
|
|
85
|
+
</PktAccordionItem>
|
|
86
|
+
</PktAccordion>,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
expect(container.querySelector('.pkt-accordion-item__icon--plus')).toBeInTheDocument()
|
|
90
|
+
expect(container.querySelector('.pkt-accordion-item__icon--minus')).toBeInTheDocument()
|
|
91
|
+
expect(container.querySelector('[name="chevron-thin-down"]')).not.toBeInTheDocument()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('updates item icons when skin changes', () => {
|
|
95
|
+
const { container, rerender } = render(
|
|
96
|
+
<PktAccordion skin="outlined">
|
|
97
|
+
<PktAccordionItem title="Title" id="item1">
|
|
98
|
+
Content
|
|
99
|
+
</PktAccordionItem>
|
|
100
|
+
</PktAccordion>,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
expect(container.querySelector('[name="chevron-thin-down"]')).toBeInTheDocument()
|
|
104
|
+
expect(container.querySelector('.pkt-accordion-item__icon--plus')).not.toBeInTheDocument()
|
|
105
|
+
|
|
106
|
+
rerender(
|
|
107
|
+
<PktAccordion skin="plus-minus">
|
|
108
|
+
<PktAccordionItem title="Title" id="item1">
|
|
109
|
+
Content
|
|
110
|
+
</PktAccordionItem>
|
|
111
|
+
</PktAccordion>,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(container.querySelector('[name="chevron-thin-down"]')).not.toBeInTheDocument()
|
|
115
|
+
expect(container.querySelector('.pkt-accordion-item__icon--plus')).toBeInTheDocument()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('item skin prop overrides accordion skin', () => {
|
|
119
|
+
const { container } = render(
|
|
120
|
+
<PktAccordion skin="plus-minus">
|
|
121
|
+
<PktAccordionItem title="Title" id="item1" skin="outlined">
|
|
122
|
+
Content
|
|
123
|
+
</PktAccordionItem>
|
|
124
|
+
</PktAccordion>,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
expect(container.querySelector('[name="chevron-thin-down"]')).toBeInTheDocument()
|
|
128
|
+
expect(container.querySelector('.pkt-accordion-item__icon--plus')).not.toBeInTheDocument()
|
|
129
|
+
})
|
|
79
130
|
})
|
|
80
131
|
|
|
81
132
|
describe('PktAccordionItem', () => {
|
|
@@ -20,8 +20,8 @@ export interface IPktAccordion {
|
|
|
20
20
|
className?: string
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// Send name som context til AccordionItem-children
|
|
24
|
-
const AccordionContext = createContext<{ name?: string }>({})
|
|
23
|
+
// Send name og skin som context til AccordionItem-children
|
|
24
|
+
const AccordionContext = createContext<{ name?: string; skin?: TPktAccordionSkin }>({})
|
|
25
25
|
|
|
26
26
|
export const useAccordionContext = () => useContext(AccordionContext)
|
|
27
27
|
|
|
@@ -37,7 +37,7 @@ export const PktAccordion = forwardRef<HTMLDivElement, IPktAccordion>(
|
|
|
37
37
|
.join(' ')
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
|
-
<AccordionContext.Provider value={{ name }}>
|
|
40
|
+
<AccordionContext.Provider value={{ name, skin }}>
|
|
41
41
|
<div
|
|
42
42
|
ref={ref}
|
|
43
43
|
className={accordionClasses}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { forwardRef, type MouseEvent, type ReactNode, type SyntheticEvent,
|
|
3
|
+
import { forwardRef, type MouseEvent, type ReactNode, type SyntheticEvent, useState } from 'react'
|
|
4
4
|
import type { TAccordionSkin } from 'shared-types'
|
|
5
5
|
|
|
6
6
|
import { PktIcon } from '../icon/Icon'
|
|
@@ -40,20 +40,17 @@ export const PktAccordionItem = forwardRef<HTMLDetailsElement, IPktAccordionItem
|
|
|
40
40
|
ref,
|
|
41
41
|
) => {
|
|
42
42
|
const [internalIsOpen, setInternalIsOpen] = useState(defaultOpen)
|
|
43
|
-
const { name: contextName } = useAccordionContext()
|
|
43
|
+
const { name: contextName, skin: contextSkin } = useAccordionContext()
|
|
44
44
|
|
|
45
45
|
// Bruk name fra props eller fra context
|
|
46
46
|
const actualName = name || contextName
|
|
47
47
|
|
|
48
|
+
// Bruk skin fra props eller fra context
|
|
49
|
+
const actualSkin = skin || contextSkin
|
|
50
|
+
|
|
48
51
|
// Bruk controlled isOpen om prop, ellers bruk intern state
|
|
49
52
|
const isOpen = controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen
|
|
50
53
|
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
if (controlledIsOpen === undefined) {
|
|
53
|
-
setInternalIsOpen(defaultOpen)
|
|
54
|
-
}
|
|
55
|
-
}, [defaultOpen, controlledIsOpen])
|
|
56
|
-
|
|
57
54
|
const handleToggle = (e: SyntheticEvent<HTMLDetailsElement>) => {
|
|
58
55
|
const detailsElement = e.currentTarget
|
|
59
56
|
const newOpenState = detailsElement.open
|
|
@@ -96,7 +93,7 @@ export const PktAccordionItem = forwardRef<HTMLDetailsElement, IPktAccordionItem
|
|
|
96
93
|
>
|
|
97
94
|
<summary className="pkt-accordion-item__title" id={`pkt-accordion-item-summary-${id}`}>
|
|
98
95
|
{title}
|
|
99
|
-
{
|
|
96
|
+
{actualSkin === 'plus-minus' ? (
|
|
100
97
|
<>
|
|
101
98
|
<PktIcon
|
|
102
99
|
name="plus-sign"
|
|
@@ -110,11 +107,7 @@ export const PktAccordionItem = forwardRef<HTMLDetailsElement, IPktAccordionItem
|
|
|
110
107
|
/>
|
|
111
108
|
</>
|
|
112
109
|
) : (
|
|
113
|
-
<PktIcon
|
|
114
|
-
name="chevron-thin-down"
|
|
115
|
-
className="pkt-accordion-item__icon"
|
|
116
|
-
aria-hidden="true"
|
|
117
|
-
/>
|
|
110
|
+
<PktIcon name="chevron-thin-down" className="pkt-accordion-item__icon" aria-hidden="true" />
|
|
118
111
|
)}
|
|
119
112
|
</summary>
|
|
120
113
|
<div className="pkt-accordion-item__content" id={`pkt-accordion-item__content-${id}`} role="region">
|