Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support links in checkbox/boolean nodes #251

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/elements-react/src/tests/jest/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => (
<OryComponentProvider components={OryDefaultComponents}>
{children}
</OryComponentProvider>
<IntlProvider locale="en">
<OryComponentProvider components={OryDefaultComponents}>
{children}
</OryComponentProvider>
</IntlProvider>
)

const customRender = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -82,7 +83,7 @@ export const DefaultCheckbox = ({
</div>
<div className="text-sm items-center">
<label className="text-sm font-normal leading-normal text-forms-fg-default">
{label && uiTextToFormattedMessage(label, intl)}
<CheckboxLabel label={label} />
</label>
{node.messages.map((message) => (
<span
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`computeLabelElements handles a label with no text but a link 1`] = `
<div>
<a
class="text-links-link-default hover:text-links-link-hover hover:underline"
href="https://example.com"
rel="noopener noreferrer"
target="_blank"
>
Click here
</a>
</div>
`;

exports[`computeLabelElements renders a text with a single markdown link correctly 1`] = `
<div>
This is a
<a
class="text-links-link-default hover:text-links-link-hover hover:underline"
href="https://example.com"
rel="noopener noreferrer"
target="_blank"
>
link
</a>
</div>
`;

exports[`computeLabelElements renders a text with link and extra text around it correctly 1`] = `
<div>
Click
<a
class="text-links-link-default hover:text-links-link-hover hover:underline"
href="https://example.com"
rel="noopener noreferrer"
target="_blank"
>
here
</a>
to visit, or go elsewhere.
</div>
`;

exports[`computeLabelElements renders a text with multiple markdown links correctly 1`] = `
<div>
This
<a
class="text-links-link-default hover:text-links-link-hover hover:underline"
href="https://first.com"
rel="noopener noreferrer"
target="_blank"
>
first link
</a>
and this
<a
class="text-links-link-default hover:text-links-link-hover hover:underline"
href="https://second.com"
rel="noopener noreferrer"
target="_blank"
>
second link
</a>
</div>
`;

exports[`computeLabelElements renders null if label is undefined 1`] = `<div />`;

exports[`computeLabelElements renders plain text without links correctly 1`] = `
<div>
This is just plain text
</div>
`;
Original file line number Diff line number Diff line change
@@ -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(
<CheckboxLabel label={{ text: labelText, id: 0, type: "info" }} />,
)
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(
<CheckboxLabel label={{ text: labelText, id: 0, type: "info" }} />,
)
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(
<CheckboxLabel label={{ text: labelText, id: 0, type: "info" }} />,
)
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(
<CheckboxLabel label={{ text: labelText, id: 0, type: "info" }} />,
)
expect(container).toMatchSnapshot()
})

test("handles a label with no text but a link", () => {
const labelText = "[Click here](https://example.com)"

const { container } = render(
<CheckboxLabel label={{ text: labelText, id: 0, type: "info" }} />,
)
expect(container).toMatchSnapshot()
})

test("renders null if label is undefined", () => {
const { container } = render(<CheckboxLabel label={undefined} />)
expect(container).toMatchSnapshot()
})
})
Original file line number Diff line number Diff line change
@@ -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

Check warning on line 26 in packages/elements-react/src/theme/default/components/ui/checkbox-label.tsx

View check run for this annotation

Codecov / codecov/patch

packages/elements-react/src/theme/default/components/ui/checkbox-label.tsx#L26

Added line #L26 was not covered by tests
}

// Push the text before the match
if (matchStart > lastIndex) {
elements.push(labelText.slice(lastIndex, matchStart))
}

// Push the <a> tag for the markdown link
elements.push(
<a
key={matchStart}
href={url}
target="_blank"
rel="noopener noreferrer"
className="text-links-link-default hover:text-links-link-hover hover:underline"
>
{linkText}
</a>,
)

// 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)}</>
}
Loading