diff --git a/packages/elements-react/src/tests/jest/test-utils.tsx b/packages/elements-react/src/tests/jest/test-utils.tsx index 3f1cc96e..589642f6 100644 --- a/packages/elements-react/src/tests/jest/test-utils.tsx +++ b/packages/elements-react/src/tests/jest/test-utils.tsx @@ -7,11 +7,14 @@ import { OryProvider, OryProviderProps } from "../../context" import { OryComponentProvider } from "../../context/component" import { OryDefaultComponents } from "../../theme/default" import { OryClientConfiguration } from "../../util" +import { IntlProvider } from "../../context/intl-context" const AllProviders = ({ children }: PropsWithChildren) => ( - - {children} - + + + {children} + + ) const customRender = ( diff --git a/packages/elements-react/src/theme/default/components/form/checkbox.tsx b/packages/elements-react/src/theme/default/components/form/checkbox.tsx index 2da0c0f5..0437dcef 100644 --- a/packages/elements-react/src/theme/default/components/form/checkbox.tsx +++ b/packages/elements-react/src/theme/default/components/form/checkbox.tsx @@ -4,14 +4,15 @@ "use client" import { getNodeLabel } from "@ory/client-fetch" import { - OryNodeInputProps, messageTestId, + OryNodeInputProps, uiTextToFormattedMessage, } from "@ory/elements-react" import { useState } from "react" import { useForm } from "react-hook-form" import { useIntl } from "react-intl" import { cn } from "../../utils/cn" +import { CheckboxLabel } from "../ui/checkbox-label" function CheckboxSVG() { return ( @@ -82,7 +83,7 @@ export const DefaultCheckbox = ({
{node.messages.map((message) => ( + + Click here + +
+`; + +exports[`computeLabelElements renders a text with a single markdown link correctly 1`] = ` +
+ This is a + + link + +
+`; + +exports[`computeLabelElements renders a text with link and extra text around it correctly 1`] = ` +
+ Click + + here + + to visit, or go elsewhere. +
+`; + +exports[`computeLabelElements renders a text with multiple markdown links correctly 1`] = ` +
+ This + + first link + + and this + + second link + +
+`; + +exports[`computeLabelElements renders null if label is undefined 1`] = `
`; + +exports[`computeLabelElements renders plain text without links correctly 1`] = ` +
+ This is just plain text +
+`; diff --git a/packages/elements-react/src/theme/default/components/ui/checkbox-label.spec.tsx b/packages/elements-react/src/theme/default/components/ui/checkbox-label.spec.tsx new file mode 100644 index 00000000..459609f2 --- /dev/null +++ b/packages/elements-react/src/theme/default/components/ui/checkbox-label.spec.tsx @@ -0,0 +1,59 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { render } from "../../../../tests/jest/test-utils" +import { CheckboxLabel } from "./checkbox-label" + +describe("computeLabelElements", () => { + test("renders plain text without links correctly", () => { + const labelText = "This is just plain text" + + const { container } = render( + , + ) + expect(container).toMatchSnapshot() + }) + + test("renders a text with a single markdown link correctly", () => { + const labelText = "This is a [link](https://example.com)" + + const { container } = render( + , + ) + expect(container).toMatchSnapshot() + }) + + test("renders a text with multiple markdown links correctly", () => { + const labelText = + "This [first link](https://first.com) and this [second link](https://second.com)" + + const { container } = render( + , + ) + expect(container).toMatchSnapshot() + }) + + test("renders a text with link and extra text around it correctly", () => { + const labelText = + "Click [here](https://example.com) to visit, or go elsewhere." + + const { container } = render( + , + ) + expect(container).toMatchSnapshot() + }) + + test("handles a label with no text but a link", () => { + const labelText = "[Click here](https://example.com)" + + const { container } = render( + , + ) + expect(container).toMatchSnapshot() + }) + + test("renders null if label is undefined", () => { + const { container } = render() + expect(container).toMatchSnapshot() + }) +}) diff --git a/packages/elements-react/src/theme/default/components/ui/checkbox-label.tsx b/packages/elements-react/src/theme/default/components/ui/checkbox-label.tsx new file mode 100644 index 00000000..afbe01e4 --- /dev/null +++ b/packages/elements-react/src/theme/default/components/ui/checkbox-label.tsx @@ -0,0 +1,67 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { UiText } from "@ory/client-fetch" +import { useIntl } from "react-intl" +import { uiTextToFormattedMessage } from "../../../../util" + +type CheckboxLabelProps = { + label?: UiText +} + +const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g + +export function computeLabelElements(labelText: string) { + const elements = [] + let lastIndex = 0 + + // Use matchAll to find all markdown links + for (const match of labelText.matchAll(linkRegex)) { + const linkText = match[1] + const url = match[2] + const matchStart = match.index + if (typeof matchStart === "undefined") { + // Some types seem to be wrong somewhere, eslint complains that matchStart can be undefined, but it can't? + // So we just skip this match, if it is undefined + continue + } + + // Push the text before the match + if (matchStart > lastIndex) { + elements.push(labelText.slice(lastIndex, matchStart)) + } + + // Push the tag for the markdown link + elements.push( + + {linkText} + , + ) + + // Update lastIndex to the end of the current match + lastIndex = matchStart + match[0].length + } + + // Push any remaining text after the last match + if (lastIndex < labelText.length) { + elements.push(labelText.slice(lastIndex)) + } + return elements +} + +export function CheckboxLabel({ label }: CheckboxLabelProps) { + const intl = useIntl() + if (!label) { + return null + } + + const labelText = uiTextToFormattedMessage(label, intl) + + return <>{computeLabelElements(labelText)} +}