@oslokommune/punkt-react 16.24.0 → 16.25.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 +35 -0
- package/dist/index.d.ts +6 -0
- package/dist/punkt-react.cjs +1 -1
- package/dist/punkt-react.js +38 -25
- package/package.json +2 -2
- package/src/components/calendar/useCalendarState.ts +17 -6
- package/src/components/header/HeaderService.test.tsx +82 -0
- package/src/components/header/HeaderService.tsx +19 -0
- package/src/components/link/Link.test.tsx +22 -0
- package/src/components/link/Link.tsx +30 -10
package/dist/punkt-react.js
CHANGED
|
@@ -59,7 +59,7 @@ var j = /* @__PURE__ */ O((/* @__PURE__ */ E(((e, t) => {
|
|
|
59
59
|
return n;
|
|
60
60
|
}) : window.classNames = n;
|
|
61
61
|
})();
|
|
62
|
-
})))(), 1), M = "https://punkt-cdn.oslo.kommune.no/16.
|
|
62
|
+
})))(), 1), M = "https://punkt-cdn.oslo.kommune.no/16.25/icons/";
|
|
63
63
|
typeof window < "u" && (window.pktFetch = window.pktFetch === void 0 ? fetch : window.pktFetch, window.pktIconPath = window.pktIconPath || M);
|
|
64
64
|
var te = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\"></svg>", ne = {}, re = 2, ie = 1500, ae = typeof Storage < "u" && typeof sessionStorage < "u", oe = typeof window < "u" ? f : l, N = (e, t) => !e || !ae ? "" : sessionStorage.getItem(t + e + ".svg") ?? "", se = (e) => window.pktFetch(e).then((t) => {
|
|
65
65
|
if (!t.ok) throw Error("Missing icon: " + e);
|
|
@@ -174,7 +174,7 @@ var I = a(({ defaultOpen: e = !1, id: t, title: n, skin: r, compact: i = !1, isO
|
|
|
174
174
|
});
|
|
175
175
|
//#endregion
|
|
176
176
|
//#region src/components/button/Button.tsx
|
|
177
|
-
I.displayName = "PktAccordionItem", typeof window < "u" && (window.pktAnimationPath = window.pktAnimationPath || "https://punkt-cdn.oslo.kommune.no/16.
|
|
177
|
+
I.displayName = "PktAccordionItem", typeof window < "u" && (window.pktAnimationPath = window.pktAnimationPath || "https://punkt-cdn.oslo.kommune.no/16.25/animations/");
|
|
178
178
|
var L = a(({ children: e, className: t, iconName: n = "user", secondIconName: r = "user", iconPath: i, secondIconPath: a, size: o = "medium", fullWidth: s = !1, fullWidthOnMobile: c = !1, skin: l = "primary", type: u = "button", variant: d = "label-only", state: f, color: p, isLoading: m = void 0, disabled: h = void 0, loadingAnimationPath: g = window.pktAnimationPath, ...y }, b) => {
|
|
179
179
|
let x = [
|
|
180
180
|
t,
|
|
@@ -2106,7 +2106,8 @@ function on(e) {
|
|
|
2106
2106
|
t !== null && Y(e.nativeEvent, t);
|
|
2107
2107
|
}, [z, Y]),
|
|
2108
2108
|
handleFocusOut: s((e) => {
|
|
2109
|
-
|
|
2109
|
+
let t = e.target, n = e.relatedTarget;
|
|
2110
|
+
P.current && P.current.contains(n) || t.classList.contains("pkt-hide") || t.closest(".pkt-cal-month-picker") || z();
|
|
2110
2111
|
}, [z]),
|
|
2111
2112
|
close: z,
|
|
2112
2113
|
setFocusedDate: le
|
|
@@ -6097,23 +6098,30 @@ function Ui(e) {
|
|
|
6097
6098
|
}
|
|
6098
6099
|
//#endregion
|
|
6099
6100
|
//#region src/components/link/Link.tsx
|
|
6100
|
-
var Wi = ({ href: e, iconName: t, className: n, iconPosition: r, external: i, target: a,
|
|
6101
|
-
let
|
|
6101
|
+
var Wi = ({ href: e, iconName: t, className: n, iconPosition: r, external: i, target: a, renderLink: o, children: s, ...c }) => {
|
|
6102
|
+
let l = {
|
|
6102
6103
|
"pkt-link": !0,
|
|
6103
6104
|
"pkt-link--icon-left": !!t && r === "left" || !!(t && !r),
|
|
6104
6105
|
"pkt-link--icon-right": !!t && r === "right",
|
|
6105
6106
|
"pkt-link--external": i
|
|
6106
|
-
}
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6107
|
+
}, u = /* @__PURE__ */ v(g, { children: [t && /* @__PURE__ */ _(F, {
|
|
6108
|
+
name: t,
|
|
6109
|
+
className: "pkt-link__icon"
|
|
6110
|
+
}), s] });
|
|
6111
|
+
return (o || (({ href: e, className: t, children: n, props: r }) => /* @__PURE__ */ _("a", {
|
|
6110
6112
|
href: e,
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
children:
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6113
|
+
className: t,
|
|
6114
|
+
...r,
|
|
6115
|
+
children: n
|
|
6116
|
+
})))({
|
|
6117
|
+
href: e ?? "",
|
|
6118
|
+
className: (0, j.default)(l, n),
|
|
6119
|
+
children: u,
|
|
6120
|
+
props: {
|
|
6121
|
+
...c,
|
|
6122
|
+
target: a,
|
|
6123
|
+
rel: i ? "noopener noreferrer" : void 0
|
|
6124
|
+
}
|
|
6117
6125
|
});
|
|
6118
6126
|
};
|
|
6119
6127
|
Wi.displayName = "PktLink";
|
|
@@ -6402,6 +6410,10 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6402
6410
|
let r = t.current.getBoundingClientRect(), i = n.current.offsetWidth, a = r.left + i > window.innerWidth;
|
|
6403
6411
|
e === "slot" ? V(a) : fe(a);
|
|
6404
6412
|
}, [Z, X]), ye = s((e) => {
|
|
6413
|
+
if (!Z || P !== "slot") return;
|
|
6414
|
+
let t = e.target.closest("a[href], button, [role=\"link\"], [role=\"button\"], [role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"menuitemcheckbox\"]");
|
|
6415
|
+
!t || !K.current?.contains(t) || t.closest("[data-pkt-header-keep-open]") || t.disabled || t.getAttribute("aria-disabled") === "true" || I("none");
|
|
6416
|
+
}, [Z, P]), be = s((e) => {
|
|
6405
6417
|
P === "none" ? (q.current = document.activeElement, I(e)) : I("none");
|
|
6406
6418
|
}, [P]);
|
|
6407
6419
|
l(() => {
|
|
@@ -6409,7 +6421,7 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6409
6421
|
ve(P);
|
|
6410
6422
|
});
|
|
6411
6423
|
}, [P, ve]);
|
|
6412
|
-
let
|
|
6424
|
+
let xe = /* @__PURE__ */ v("header", {
|
|
6413
6425
|
className: (0, j.default)("pkt-header-service", i && "pkt-header-service--compact", X && "pkt-header-service--mobile", Z && "pkt-header-service--tablet", se && "pkt-header-service--fixed", ce && "pkt-header-service--scroll-to-hide", ue && "pkt-header-service--hidden", r),
|
|
6414
6426
|
"data-mode": a,
|
|
6415
6427
|
ref: he,
|
|
@@ -6424,7 +6436,7 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6424
6436
|
children: /* @__PURE__ */ _(F, {
|
|
6425
6437
|
name: "oslologo",
|
|
6426
6438
|
"aria-hidden": "true",
|
|
6427
|
-
path: "https://punkt-cdn.oslo.kommune.no/16.
|
|
6439
|
+
path: "https://punkt-cdn.oslo.kommune.no/16.25/logos/"
|
|
6428
6440
|
})
|
|
6429
6441
|
}) : ne ? /* @__PURE__ */ _("button", {
|
|
6430
6442
|
"aria-label": "Tilbake til forside",
|
|
@@ -6433,12 +6445,12 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6433
6445
|
children: /* @__PURE__ */ _(F, {
|
|
6434
6446
|
name: "oslologo",
|
|
6435
6447
|
"aria-hidden": "true",
|
|
6436
|
-
path: "https://punkt-cdn.oslo.kommune.no/16.
|
|
6448
|
+
path: "https://punkt-cdn.oslo.kommune.no/16.25/logos/"
|
|
6437
6449
|
})
|
|
6438
6450
|
}) : /* @__PURE__ */ _(F, {
|
|
6439
6451
|
name: "oslologo",
|
|
6440
6452
|
"aria-hidden": "true",
|
|
6441
|
-
path: "https://punkt-cdn.oslo.kommune.no/16.
|
|
6453
|
+
path: "https://punkt-cdn.oslo.kommune.no/16.25/logos/"
|
|
6442
6454
|
})
|
|
6443
6455
|
}), T && /* @__PURE__ */ _("span", {
|
|
6444
6456
|
className: "pkt-header-service__service-name",
|
|
@@ -6470,7 +6482,7 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6470
6482
|
iconName: "menu",
|
|
6471
6483
|
size: X ? "small" : "medium",
|
|
6472
6484
|
state: P === "slot" ? "active" : "normal",
|
|
6473
|
-
onClick: () =>
|
|
6485
|
+
onClick: () => be("slot"),
|
|
6474
6486
|
"aria-expanded": P === "slot",
|
|
6475
6487
|
"aria-controls": "mobile-slot-menu",
|
|
6476
6488
|
"aria-label": "Åpne meny",
|
|
@@ -6481,6 +6493,7 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6481
6493
|
role: Z ? "menu" : void 0,
|
|
6482
6494
|
"aria-label": Z ? "Meny" : void 0,
|
|
6483
6495
|
ref: K,
|
|
6496
|
+
onClick: ye,
|
|
6484
6497
|
children: /* @__PURE__ */ _("div", {
|
|
6485
6498
|
className: "pkt-contents",
|
|
6486
6499
|
children: n
|
|
@@ -6496,7 +6509,7 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6496
6509
|
variant: "icon-only",
|
|
6497
6510
|
iconName: "magnifying-glass-big",
|
|
6498
6511
|
size: X ? "small" : "medium",
|
|
6499
|
-
onClick: () =>
|
|
6512
|
+
onClick: () => be("search"),
|
|
6500
6513
|
state: P === "search" ? "active" : "normal",
|
|
6501
6514
|
"aria-expanded": P === "search",
|
|
6502
6515
|
"aria-controls": "mobile-search-menu",
|
|
@@ -6558,7 +6571,7 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6558
6571
|
variant: "icons-right-and-left",
|
|
6559
6572
|
iconName: "user",
|
|
6560
6573
|
secondIconName: P === "user" ? "chevron-thin-up" : "chevron-thin-down",
|
|
6561
|
-
onClick: () =>
|
|
6574
|
+
onClick: () => be("user"),
|
|
6562
6575
|
children: [/* @__PURE__ */ _("span", {
|
|
6563
6576
|
className: "pkt-sr-only",
|
|
6564
6577
|
children: "Brukermeny: "
|
|
@@ -6591,8 +6604,8 @@ var Xi = a(({ mobileBreakpoint: e = 768, tabletBreakpoint: t = 1280, children: n
|
|
|
6591
6604
|
});
|
|
6592
6605
|
return se ? /* @__PURE__ */ v("div", {
|
|
6593
6606
|
className: "pkt-header-service-wrapper",
|
|
6594
|
-
children: [
|
|
6595
|
-
}) :
|
|
6607
|
+
children: [xe, /* @__PURE__ */ _("div", { className: (0, j.default)("pkt-header-service-spacer", i && "pkt-header-service-spacer--compact", E && "pkt-header-service-spacer--has-user", X && "pkt-header-service-spacer--mobile", Z && "pkt-header-service-spacer--tablet") })]
|
|
6608
|
+
}) : xe;
|
|
6596
6609
|
});
|
|
6597
6610
|
Xi.displayName = "PktHeaderService";
|
|
6598
6611
|
//#endregion
|
|
@@ -6938,7 +6951,7 @@ var ya = ({ children: e, skin: t, title: n, href: r, iconName: i, openInNewTab:
|
|
|
6938
6951
|
};
|
|
6939
6952
|
//#endregion
|
|
6940
6953
|
//#region src/components/loader/Loader.tsx
|
|
6941
|
-
ya.displayName = "PktLinkCard", typeof window < "u" && (window.pktAnimationPath = window.pktAnimationPath || "https://punkt-cdn.oslo.kommune.no/16.
|
|
6954
|
+
ya.displayName = "PktLinkCard", typeof window < "u" && (window.pktAnimationPath = window.pktAnimationPath || "https://punkt-cdn.oslo.kommune.no/16.25/animations/");
|
|
6942
6955
|
var ba = (e) => {
|
|
6943
6956
|
switch (e) {
|
|
6944
6957
|
case "blue": return "spinner-blue";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-react",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.25.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",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
101
101
|
},
|
|
102
102
|
"license": "MIT",
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "d01de2b51e9c71b709202eb0d2fb3336e2932a20"
|
|
104
104
|
}
|
|
@@ -372,13 +372,24 @@ export function useCalendarState(props: IPktCalendar): ICalendarState {
|
|
|
372
372
|
|
|
373
373
|
const handleFocusOut = useCallback(
|
|
374
374
|
(e: React.FocusEvent) => {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
375
|
+
const target = e.target as Element
|
|
376
|
+
const relatedTarget = e.relatedTarget as Node | null
|
|
377
|
+
|
|
378
|
+
if (calendarRef.current && calendarRef.current.contains(relatedTarget)) {
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (target.classList.contains('pkt-hide')) {
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Don't close if focus is coming from month/year picker controls
|
|
387
|
+
// Safari iOS triggers focusout with null relatedTarget when select changes
|
|
388
|
+
if (target.closest('.pkt-cal-month-picker')) {
|
|
389
|
+
return
|
|
381
390
|
}
|
|
391
|
+
|
|
392
|
+
close()
|
|
382
393
|
},
|
|
383
394
|
[close],
|
|
384
395
|
)
|
|
@@ -512,4 +512,86 @@ describe('PktHeaderService', () => {
|
|
|
512
512
|
expect(logoutButtons.length).toBe(0)
|
|
513
513
|
})
|
|
514
514
|
})
|
|
515
|
+
|
|
516
|
+
// Closing the slot menu on intentional clicks inside the dropdown (mobile/tablet)
|
|
517
|
+
describe('slot menu closes on intentional click', () => {
|
|
518
|
+
const openSlotMenu = () => {
|
|
519
|
+
fireEvent.click(screen.getByRole('button', { name: 'Åpne meny' }))
|
|
520
|
+
expect(screen.getByRole('button', { name: 'Åpne meny' })).toHaveAttribute('aria-expanded', 'true')
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
beforeEach(() => {
|
|
524
|
+
mockedWidth = 500
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it('closes when clicking a link inside the menu', () => {
|
|
528
|
+
render(
|
|
529
|
+
<PktHeaderService serviceName="Svc">
|
|
530
|
+
<a href="#">Gå hit</a>
|
|
531
|
+
</PktHeaderService>,
|
|
532
|
+
)
|
|
533
|
+
openSlotMenu()
|
|
534
|
+
fireEvent.click(screen.getByText('Gå hit'))
|
|
535
|
+
expect(screen.getByRole('button', { name: 'Åpne meny' })).toHaveAttribute('aria-expanded', 'false')
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
it('closes when clicking a button inside the menu', () => {
|
|
539
|
+
render(
|
|
540
|
+
<PktHeaderService serviceName="Svc">
|
|
541
|
+
<button type="button">Gjør noe</button>
|
|
542
|
+
</PktHeaderService>,
|
|
543
|
+
)
|
|
544
|
+
openSlotMenu()
|
|
545
|
+
fireEvent.click(screen.getByText('Gjør noe'))
|
|
546
|
+
expect(screen.getByRole('button', { name: 'Åpne meny' })).toHaveAttribute('aria-expanded', 'false')
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
it('stays open when clicking a form input inside the menu', () => {
|
|
550
|
+
render(
|
|
551
|
+
<PktHeaderService serviceName="Svc">
|
|
552
|
+
<input type="text" aria-label="Filtrer" />
|
|
553
|
+
</PktHeaderService>,
|
|
554
|
+
)
|
|
555
|
+
openSlotMenu()
|
|
556
|
+
fireEvent.click(screen.getByLabelText('Filtrer'))
|
|
557
|
+
expect(screen.getByRole('button', { name: 'Åpne meny' })).toHaveAttribute('aria-expanded', 'true')
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
it('stays open when clicking non-interactive content inside the menu', () => {
|
|
561
|
+
render(
|
|
562
|
+
<PktHeaderService serviceName="Svc">
|
|
563
|
+
<span>Bare tekst</span>
|
|
564
|
+
</PktHeaderService>,
|
|
565
|
+
)
|
|
566
|
+
openSlotMenu()
|
|
567
|
+
fireEvent.click(screen.getByText('Bare tekst'))
|
|
568
|
+
expect(screen.getByRole('button', { name: 'Åpne meny' })).toHaveAttribute('aria-expanded', 'true')
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
it('stays open when clicking an element marked data-pkt-header-keep-open', () => {
|
|
572
|
+
render(
|
|
573
|
+
<PktHeaderService serviceName="Svc">
|
|
574
|
+
<button type="button" data-pkt-header-keep-open>
|
|
575
|
+
Vis mer
|
|
576
|
+
</button>
|
|
577
|
+
</PktHeaderService>,
|
|
578
|
+
)
|
|
579
|
+
openSlotMenu()
|
|
580
|
+
fireEvent.click(screen.getByText('Vis mer'))
|
|
581
|
+
expect(screen.getByRole('button', { name: 'Åpne meny' })).toHaveAttribute('aria-expanded', 'true')
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it('stays open when clicking a disabled button inside the menu', () => {
|
|
585
|
+
render(
|
|
586
|
+
<PktHeaderService serviceName="Svc">
|
|
587
|
+
<button type="button" disabled>
|
|
588
|
+
Utilgjengelig
|
|
589
|
+
</button>
|
|
590
|
+
</PktHeaderService>,
|
|
591
|
+
)
|
|
592
|
+
openSlotMenu()
|
|
593
|
+
fireEvent.click(screen.getByText('Utilgjengelig'))
|
|
594
|
+
expect(screen.getByRole('button', { name: 'Åpne meny' })).toHaveAttribute('aria-expanded', 'true')
|
|
595
|
+
})
|
|
596
|
+
})
|
|
515
597
|
})
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ForwardedRef,
|
|
8
8
|
forwardRef,
|
|
9
9
|
type KeyboardEvent,
|
|
10
|
+
type MouseEvent as ReactMouseEvent,
|
|
10
11
|
type MutableRefObject,
|
|
11
12
|
type RefObject,
|
|
12
13
|
useCallback,
|
|
@@ -273,6 +274,23 @@ export const PktHeaderService = forwardRef(
|
|
|
273
274
|
[isTablet, isMobile],
|
|
274
275
|
)
|
|
275
276
|
|
|
277
|
+
// Close the slot menu when the user makes an intentional click inside it.
|
|
278
|
+
const handleSlotContentClick = useCallback(
|
|
279
|
+
(event: ReactMouseEvent<HTMLDivElement>) => {
|
|
280
|
+
if (!isTablet || openMenu !== 'slot') return
|
|
281
|
+
|
|
282
|
+
const interactive = (event.target as HTMLElement).closest<HTMLElement>(
|
|
283
|
+
'a[href], button, [role="link"], [role="button"], [role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"]',
|
|
284
|
+
)
|
|
285
|
+
if (!interactive || !slotContentRef.current?.contains(interactive)) return
|
|
286
|
+
if (interactive.closest('[data-pkt-header-keep-open]')) return
|
|
287
|
+
if ((interactive as HTMLButtonElement).disabled || interactive.getAttribute('aria-disabled') === 'true') return
|
|
288
|
+
|
|
289
|
+
setOpenMenu('none')
|
|
290
|
+
},
|
|
291
|
+
[isTablet, openMenu],
|
|
292
|
+
)
|
|
293
|
+
|
|
276
294
|
const handleMenuToggle = useCallback(
|
|
277
295
|
(mode: THeaderMenu) => {
|
|
278
296
|
if (openMenu !== 'none') {
|
|
@@ -393,6 +411,7 @@ export const PktHeaderService = forwardRef(
|
|
|
393
411
|
role={isTablet ? 'menu' : undefined}
|
|
394
412
|
aria-label={isTablet ? 'Meny' : undefined}
|
|
395
413
|
ref={slotContentRef}
|
|
414
|
+
onClick={handleSlotContentClick}
|
|
396
415
|
>
|
|
397
416
|
<div className="pkt-contents">{children}</div>
|
|
398
417
|
</div>
|
|
@@ -109,4 +109,26 @@ describe('PktLink', () => {
|
|
|
109
109
|
const link = screen.getByTestId('custom-link')
|
|
110
110
|
expect(link).toHaveAttribute('aria-label', 'Custom label')
|
|
111
111
|
})
|
|
112
|
+
|
|
113
|
+
it('should use renderLink to render a custom link element', () => {
|
|
114
|
+
render(
|
|
115
|
+
<PktLink
|
|
116
|
+
href="/test"
|
|
117
|
+
iconName="arrow"
|
|
118
|
+
renderLink={({ href, className, children, props }) => (
|
|
119
|
+
<a href={href} className={className} data-custom="true" {...props}>
|
|
120
|
+
{children}
|
|
121
|
+
</a>
|
|
122
|
+
)}
|
|
123
|
+
>
|
|
124
|
+
Test Link
|
|
125
|
+
</PktLink>,
|
|
126
|
+
)
|
|
127
|
+
const link = screen.getByText('Test Link').closest('a')
|
|
128
|
+
expect(link).toHaveAttribute('href', '/test')
|
|
129
|
+
expect(link).toHaveAttribute('data-custom', 'true')
|
|
130
|
+
expect(link).toHaveClass('pkt-link')
|
|
131
|
+
expect(link).toHaveClass('pkt-link--icon-left')
|
|
132
|
+
expect(document.querySelector('.pkt-link__icon')).toBeInTheDocument()
|
|
133
|
+
})
|
|
112
134
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames'
|
|
4
|
-
import { FC, LinkHTMLAttributes } from 'react'
|
|
4
|
+
import { AnchorHTMLAttributes, FC, LinkHTMLAttributes, ReactNode } from 'react'
|
|
5
5
|
|
|
6
6
|
import { PktIcon } from '../icon/Icon'
|
|
7
7
|
|
|
@@ -12,6 +12,12 @@ interface IPktLink extends LinkHTMLAttributes<HTMLAnchorElement> {
|
|
|
12
12
|
iconPosition?: string | undefined
|
|
13
13
|
external?: boolean
|
|
14
14
|
target?: string | undefined
|
|
15
|
+
renderLink?: (args: {
|
|
16
|
+
href: string
|
|
17
|
+
className: string
|
|
18
|
+
children: ReactNode
|
|
19
|
+
props: AnchorHTMLAttributes<HTMLAnchorElement>
|
|
20
|
+
}) => ReactNode
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
export const PktLink: FC<IPktLink> = ({
|
|
@@ -21,6 +27,7 @@ export const PktLink: FC<IPktLink> = ({
|
|
|
21
27
|
iconPosition,
|
|
22
28
|
external,
|
|
23
29
|
target,
|
|
30
|
+
renderLink,
|
|
24
31
|
children,
|
|
25
32
|
...props
|
|
26
33
|
}: IPktLink) => {
|
|
@@ -31,18 +38,31 @@ export const PktLink: FC<IPktLink> = ({
|
|
|
31
38
|
'pkt-link--external': external,
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{...props}
|
|
37
|
-
className={classNames(classes, className)}
|
|
38
|
-
href={href}
|
|
39
|
-
target={target}
|
|
40
|
-
rel={external ? 'noopener noreferrer' : undefined}
|
|
41
|
-
>
|
|
41
|
+
const linkChildren = (
|
|
42
|
+
<>
|
|
42
43
|
{iconName && <PktIcon name={iconName} className="pkt-link__icon" />}
|
|
43
44
|
{children}
|
|
44
|
-
|
|
45
|
+
</>
|
|
45
46
|
)
|
|
47
|
+
|
|
48
|
+
const linkRenderer =
|
|
49
|
+
renderLink ||
|
|
50
|
+
(({ href, className, children, props }) => (
|
|
51
|
+
<a href={href} className={className} {...props}>
|
|
52
|
+
{children}
|
|
53
|
+
</a>
|
|
54
|
+
))
|
|
55
|
+
|
|
56
|
+
return linkRenderer({
|
|
57
|
+
href: href ?? '',
|
|
58
|
+
className: classNames(classes, className),
|
|
59
|
+
children: linkChildren,
|
|
60
|
+
props: {
|
|
61
|
+
...props,
|
|
62
|
+
target,
|
|
63
|
+
rel: external ? 'noopener noreferrer' : undefined,
|
|
64
|
+
},
|
|
65
|
+
})
|
|
46
66
|
}
|
|
47
67
|
|
|
48
68
|
PktLink.displayName = 'PktLink'
|