From fab7df107cb65306b2d6c2a7537a1cb30faffbde Mon Sep 17 00:00:00 2001 From: 81reap Date: Fri, 30 Oct 2026 19:03:45 -0400 Subject: [PATCH] feat(card) :: let a card's link open a new tab Each card accepts a `target`, such as `_blank`, for its link. It works like the button component's `target`. Fixes #820 Assisted-By :: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ .../sqlpage/migrations/31_card_docs_update.sql | 1 + sqlpage/templates/card.handlebars | 2 +- tests/end-to-end/fixtures/card/target/index.sql | 3 +++ tests/end-to-end/fixtures/card/target/test.ts | 10 ++++++++++ 5 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/end-to-end/fixtures/card/target/index.sql create mode 100644 tests/end-to-end/fixtures/card/target/test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e850bcee..3128283e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ - Each item of the tracking component accepts a `link`. ([#900](https://github.com/sqlpage/SQLPage/issues/900)) +- Each card of the card component accepts a `target`, such as `_blank` to open its link in a new tab. ([#820](https://github.com/sqlpage/SQLPage/issues/820)) + - Custom components can call inline partials inside `each_row` and after it, when the partials are defined before `each_row`. ([#846](https://github.com/sqlpage/SQLPage/issues/846)) ## v0.47.0 (2026-10-15) diff --git a/examples/official-site/sqlpage/migrations/31_card_docs_update.sql b/examples/official-site/sqlpage/migrations/31_card_docs_update.sql index 1b813b8a9..44633ee2c 100644 --- a/examples/official-site/sqlpage/migrations/31_card_docs_update.sql +++ b/examples/official-site/sqlpage/migrations/31_card_docs_update.sql @@ -24,6 +24,7 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S ('footer', 'Muted text to display at the bottom of the card.', 'TEXT', FALSE, TRUE), ('footer_md', 'Muted text to display at the bottom of the card, with rich text formatting in Markdown format.', 'TEXT', FALSE, TRUE), ('link', 'An URL to which the user should be taken when they click on the card.', 'URL', FALSE, TRUE), + ('target', 'Where the card''s link opens: "_blank" for a new tab, "_self" for the same tab, "_parent" for the parent frame, or "_top" for the full body of the window.', 'TEXT', FALSE, TRUE), ('footer_link', 'An URL to which the user should be taken when they click on the footer.', 'URL', FALSE, TRUE), ('style', 'Inline style property to your iframe embed code. For example "background-color: #FFFFFF"', 'TEXT', FALSE, TRUE), ('height', 'Height of the iframe embed code, in pixels. Used only when embed_mode is ''iframe''.', 'INTEGER', FALSE, TRUE), diff --git a/sqlpage/templates/card.handlebars b/sqlpage/templates/card.handlebars index aa64fc471..d6ea5bc52 100644 --- a/sqlpage/templates/card.handlebars +++ b/sqlpage/templates/card.handlebars @@ -35,7 +35,7 @@ " {{#if (and embed (ne embed_mode "iframe"))}}data-pre-init="card" data-embed="{{embed}}"{{/if}}> {{#if link}} - + {{/if}} {{#if top_image}} diff --git a/tests/end-to-end/fixtures/card/target/index.sql b/tests/end-to-end/fixtures/card/target/index.sql new file mode 100644 index 000000000..4a97afcb8 --- /dev/null +++ b/tests/end-to-end/fixtures/card/target/index.sql @@ -0,0 +1,3 @@ +SELECT 'card' AS component; +SELECT 'In a new tab' AS title, 'https://example.com' AS link, '_blank' AS target; +SELECT 'In the same tab' AS title, 'https://example.com' AS link; diff --git a/tests/end-to-end/fixtures/card/target/test.ts b/tests/end-to-end/fixtures/card/target/test.ts new file mode 100644 index 000000000..7977053b9 --- /dev/null +++ b/tests/end-to-end/fixtures/card/target/test.ts @@ -0,0 +1,10 @@ +import { expect, test } from "../../../fixture.ts"; + +test("a card's link opens where its target says", async ({ page }) => { + await expect( + page.getByRole("link", { name: "In a new tab" }), + ).toHaveAttribute("target", "_blank"); + await expect( + page.getByRole("link", { name: "In the same tab" }), + ).not.toHaveAttribute("target"); +});