diff --git a/packages/pdf/src/templates/azurill/AzurillPage.tsx b/packages/pdf/src/templates/azurill/AzurillPage.tsx index f1469c37c..b2b0745fe 100644 --- a/packages/pdf/src/templates/azurill/AzurillPage.tsx +++ b/packages/pdf/src/templates/azurill/AzurillPage.tsx @@ -1,5 +1,4 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, @@ -12,6 +11,7 @@ import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { Fragment, useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -121,17 +121,9 @@ const Header = ({ styles }: { styles: AzurillStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/bronzor/BronzorPage.tsx b/packages/pdf/src/templates/bronzor/BronzorPage.tsx index d094267e4..fe48cc0fd 100644 --- a/packages/pdf/src/templates/bronzor/BronzorPage.tsx +++ b/packages/pdf/src/templates/bronzor/BronzorPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -109,17 +109,9 @@ const Header = ({ styles }: { styles: BronzorStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/chikorita/ChikoritaPage.tsx b/packages/pdf/src/templates/chikorita/ChikoritaPage.tsx index 79b1e6009..8ee851f12 100644 --- a/packages/pdf/src/templates/chikorita/ChikoritaPage.tsx +++ b/packages/pdf/src/templates/chikorita/ChikoritaPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { Fragment, useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -127,17 +127,9 @@ const Header = ({ styles }: { styles: ChikoritaStyles }) => { - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/ditgar/DitgarPage.tsx b/packages/pdf/src/templates/ditgar/DitgarPage.tsx index 4c85b878b..58091ada3 100644 --- a/packages/pdf/src/templates/ditgar/DitgarPage.tsx +++ b/packages/pdf/src/templates/ditgar/DitgarPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateFeatures, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { parseColorString, rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -129,17 +129,20 @@ const Header = ({ styles, colors }: { styles: DitgarStyles; colors: TemplateColo {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/ditto/DittoPage.tsx b/packages/pdf/src/templates/ditto/DittoPage.tsx index 78bc22d2d..1188f6c0d 100644 --- a/packages/pdf/src/templates/ditto/DittoPage.tsx +++ b/packages/pdf/src/templates/ditto/DittoPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { Fragment, useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -129,17 +129,9 @@ const Header = ({ styles }: { styles: DittoStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/gengar/GengarPage.tsx b/packages/pdf/src/templates/gengar/GengarPage.tsx index 526a81a64..794aa52a7 100644 --- a/packages/pdf/src/templates/gengar/GengarPage.tsx +++ b/packages/pdf/src/templates/gengar/GengarPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { Fragment, useMemo } from "react"; import { parseColorString, rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -126,17 +126,20 @@ const Header = ({ styles, colors }: { styles: GengarStyles; colors: TemplateColo {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/glalie/GlaliePage.tsx b/packages/pdf/src/templates/glalie/GlaliePage.tsx index 104c48427..2997fabbf 100644 --- a/packages/pdf/src/templates/glalie/GlaliePage.tsx +++ b/packages/pdf/src/templates/glalie/GlaliePage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateFeatures, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { parseColorString, rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -124,17 +124,9 @@ const Header = ({ styles }: { styles: GlalieStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/kakuna/KakunaPage.tsx b/packages/pdf/src/templates/kakuna/KakunaPage.tsx index 5bb9823ca..bc2460670 100644 --- a/packages/pdf/src/templates/kakuna/KakunaPage.tsx +++ b/packages/pdf/src/templates/kakuna/KakunaPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -100,17 +100,9 @@ const Header = ({ styles }: { styles: KakunaStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/lapras/LaprasPage.tsx b/packages/pdf/src/templates/lapras/LaprasPage.tsx index 85361b00e..122b467fd 100644 --- a/packages/pdf/src/templates/lapras/LaprasPage.tsx +++ b/packages/pdf/src/templates/lapras/LaprasPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { parseColorString, rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -99,17 +99,9 @@ const Header = ({ styles }: { styles: LaprasStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/leafish/LeafishPage.tsx b/packages/pdf/src/templates/leafish/LeafishPage.tsx index 58ffd10d3..e220e1f7d 100644 --- a/packages/pdf/src/templates/leafish/LeafishPage.tsx +++ b/packages/pdf/src/templates/leafish/LeafishPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { parseColorString, rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -119,17 +119,9 @@ const Header = ({ styles }: { styles: LeafishStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/meowth/MeowthPage.tsx b/packages/pdf/src/templates/meowth/MeowthPage.tsx index e88441c74..c5bdd80fa 100644 --- a/packages/pdf/src/templates/meowth/MeowthPage.tsx +++ b/packages/pdf/src/templates/meowth/MeowthPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateFeatures, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -102,17 +102,9 @@ const Header = ({ styles }: { styles: MeowthStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/onyx/OnyxPage.tsx b/packages/pdf/src/templates/onyx/OnyxPage.tsx index 740ee8646..a207a8a8a 100644 --- a/packages/pdf/src/templates/onyx/OnyxPage.tsx +++ b/packages/pdf/src/templates/onyx/OnyxPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -99,17 +99,9 @@ const Header = ({ styles }: { styles: OnyxStyles }) => { {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/pikachu/PikachuPage.tsx b/packages/pdf/src/templates/pikachu/PikachuPage.tsx index f3e1c6d44..417cd7159 100644 --- a/packages/pdf/src/templates/pikachu/PikachuPage.tsx +++ b/packages/pdf/src/templates/pikachu/PikachuPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateFeatures, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -126,17 +126,20 @@ const Header = ({ styles, colors }: { styles: PikachuStyles; colors: TemplateCol {basics.location} )} - {basics.website.url && ( - - - {basics.website.label} - - )} + {basics.customFields.map((field) => ( - - - {field.text} - + ))} diff --git a/packages/pdf/src/templates/rhyhorn/RhyhornPage.tsx b/packages/pdf/src/templates/rhyhorn/RhyhornPage.tsx index 2b40a1789..718ac8d9e 100644 --- a/packages/pdf/src/templates/rhyhorn/RhyhornPage.tsx +++ b/packages/pdf/src/templates/rhyhorn/RhyhornPage.tsx @@ -1,11 +1,11 @@ import type { Style } from "@react-pdf/types"; -import type { IconName } from "phosphor-icons-react-pdf/dynamic"; import type { TemplatePageProps } from "../../document"; import type { TemplateColorRoles, TemplateStyleContext, TemplateStyleSlots } from "../shared/types"; import { Image, Page, StyleSheet, View } from "@react-pdf/renderer"; import { useMemo } from "react"; import { rgbaStringToHex } from "@reactive-resume/utils/color"; import { useRender } from "../../context"; +import { CustomFieldContactItem, WebsiteContactItem } from "../shared/contact-item"; import { TemplateProvider } from "../shared/context"; import { filterSections } from "../shared/filtering"; import { getTemplateMetrics } from "../shared/metrics"; @@ -91,16 +91,10 @@ const Header = ({ styles }: { styles: RhyhornStyles }) => { ), basics.website.url && ( - - - {basics.website.label} - + ), ...basics.customFields.map((field) => ( - - - {field.text} - + )), ].filter(Boolean); diff --git a/packages/pdf/src/templates/shared/contact-item.tsx b/packages/pdf/src/templates/shared/contact-item.tsx new file mode 100644 index 000000000..051e1c013 --- /dev/null +++ b/packages/pdf/src/templates/shared/contact-item.tsx @@ -0,0 +1,64 @@ +import type { Style } from "@react-pdf/types"; +import type { CustomField } from "@reactive-resume/schema/resume/data"; +import type { IconName } from "phosphor-icons-react-pdf/dynamic"; +import { View } from "@react-pdf/renderer"; +import { getCustomFieldLinkUrl, getWebsiteDisplayText } from "./contact"; +import { Icon, Link, Text } from "./primitives"; + +type WebsiteDisplay = { + url: string; + label?: string | undefined; +}; + +type ContactStyle = Style | Style[]; + +export const WebsiteContactItem = ({ + website, + style, + textStyle, + iconColor, +}: { + website: WebsiteDisplay; + style?: ContactStyle; + textStyle?: ContactStyle; + iconColor?: string; +}) => { + if (!website.url) return null; + + return ( + + + {getWebsiteDisplayText(website)} + + ); +}; + +export const CustomFieldContactItem = ({ + field, + style, + textStyle, + iconColor, +}: { + field: CustomField; + style?: ContactStyle; + textStyle?: ContactStyle; + iconColor?: string; +}) => { + const linkUrl = getCustomFieldLinkUrl(field); + const children = ( + <> + + {field.text} + + ); + + if (linkUrl) { + return ( + + {children} + + ); + } + + return {children}; +}; diff --git a/packages/pdf/src/templates/shared/contact.test.ts b/packages/pdf/src/templates/shared/contact.test.ts new file mode 100644 index 000000000..f26762361 --- /dev/null +++ b/packages/pdf/src/templates/shared/contact.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { getCustomFieldLinkUrl, getWebsiteDisplayText } from "./contact"; + +describe("getWebsiteDisplayText", () => { + it("falls back to the full URL when the website label is blank", () => { + expect(getWebsiteDisplayText({ url: "https://davidkowalski.games", label: "" })).toBe( + "https://davidkowalski.games", + ); + expect(getWebsiteDisplayText({ url: "https://davidkowalski.games", label: " " })).toBe( + "https://davidkowalski.games", + ); + expect(getWebsiteDisplayText({ url: "http://example.com", label: undefined })).toBe("http://example.com"); + }); + + it("uses the website label when one is provided", () => { + expect(getWebsiteDisplayText({ url: "https://davidkowalski.games", label: "Portfolio" })).toBe("Portfolio"); + }); +}); + +describe("getCustomFieldLinkUrl", () => { + it("returns no link URL for blank custom field links", () => { + expect(getCustomFieldLinkUrl({ link: "" })).toBeUndefined(); + expect(getCustomFieldLinkUrl({ link: " " })).toBeUndefined(); + expect(getCustomFieldLinkUrl({ link: undefined })).toBeUndefined(); + }); + + it("returns the custom field link URL when one is provided", () => { + expect(getCustomFieldLinkUrl({ link: "https://example.com" })).toBe("https://example.com"); + }); +}); diff --git a/packages/pdf/src/templates/shared/contact.ts b/packages/pdf/src/templates/shared/contact.ts new file mode 100644 index 000000000..e960f315c --- /dev/null +++ b/packages/pdf/src/templates/shared/contact.ts @@ -0,0 +1,20 @@ +type WebsiteDisplay = { + url: string; + label?: string | undefined; +}; + +type CustomFieldLink = { + link?: string | undefined; +}; + +export const getWebsiteDisplayText = (website: WebsiteDisplay): string => { + const label = website.label?.trim(); + + return label || website.url; +}; + +export const getCustomFieldLinkUrl = (field: CustomFieldLink): string | undefined => { + const link = field.link?.trim(); + + return link || undefined; +}; diff --git a/packages/pdf/src/templates/shared/sections.tsx b/packages/pdf/src/templates/shared/sections.tsx index e6cc889da..fe85516af 100644 --- a/packages/pdf/src/templates/shared/sections.tsx +++ b/packages/pdf/src/templates/shared/sections.tsx @@ -25,6 +25,7 @@ import { match } from "ts-pattern"; import { useRender } from "../../context"; import { getResumeSectionTitle } from "../../section-title"; import { getSectionItemRows, getSectionItemsLayout, shouldUseSectionTimeline } from "./columns"; +import { getWebsiteDisplayText } from "./contact"; import { TemplatePlacementProvider, useTemplateFeature, @@ -225,7 +226,7 @@ const ItemTitle = ({ children, website }: { children: ReactNode; website: ItemWe const ItemWebsiteLink = ({ website }: { website: ItemWebsite }) => { if (!shouldRenderSeparateItemWebsite(website)) return null; - return {website.label || website.url}; + return {getWebsiteDisplayText(website)}; }; const SummarySection = ({ showHeading = true }: { showHeading?: boolean } = {}) => { diff --git a/packages/ui/src/components/input-group.test.tsx b/packages/ui/src/components/input-group.test.tsx new file mode 100644 index 000000000..c3e46dd98 --- /dev/null +++ b/packages/ui/src/components/input-group.test.tsx @@ -0,0 +1,34 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, test } from "vitest"; +import { createPortal } from "react-dom"; +import { InputGroup, InputGroupAddon, InputGroupInput } from "./input-group"; + +function PortaledInput() { + return createPortal(, document.body); +} + +describe("InputGroupAddon", () => { + test("does not redirect focus from controls rendered through a portal", async () => { + const user = userEvent.setup(); + + render( + + + Addon + + + + + , + ); + + const portaledInput = screen.getByLabelText("Portaled input"); + const outerInput = screen.getByLabelText("Outer input"); + + await user.click(portaledInput); + + expect(document.activeElement).toBe(portaledInput); + expect(document.activeElement).not.toBe(outerInput); + }); +}); diff --git a/packages/ui/src/components/input-group.tsx b/packages/ui/src/components/input-group.tsx index 9185e37d8..4e58b12a1 100644 --- a/packages/ui/src/components/input-group.tsx +++ b/packages/ui/src/components/input-group.tsx @@ -48,6 +48,7 @@ function InputGroupAddon({ data-slot="input-group-addon" className={cn(inputGroupAddonVariants({ align }), className)} onKeyDown={(e) => { + if (!(e.target instanceof Element) || !e.currentTarget.contains(e.target)) return; // Only respond to Space or Enter if (e.key !== " " && e.key !== "Enter") return; if (!(e.target as HTMLElement).closest("button")) { @@ -56,6 +57,7 @@ function InputGroupAddon({ } }} onClick={(e) => { + if (!(e.target instanceof Element) || !e.currentTarget.contains(e.target)) return; if (!(e.target as HTMLElement).closest("button")) { e.preventDefault(); e.currentTarget.parentElement?.querySelector("input")?.focus();