diff --git a/.changeset/fix-link-button-mobile.md b/.changeset/fix-link-button-mobile.md new file mode 100644 index 00000000..ad5be4cc --- /dev/null +++ b/.changeset/fix-link-button-mobile.md @@ -0,0 +1,5 @@ +--- +"@frigade/react": patch +--- + +Fix mobile popup-blocker swallowing primary/secondary button link clicks. When a step exposes `primaryButton.uri` (or the legacy `primaryButtonUri`) and the consumer hasn't overridden the `navigate` prop, the button now renders as a native `` so the browser handles navigation directly. Previously the click triggered `window.open` after an awaited `step.complete`, which iOS Safari and Chrome Android silently block as a popup because the user-gesture context was lost. Buttons without a URI, and buttons under a custom `navigate` handler, are unchanged. Visual styling is identical. diff --git a/apps/smithy/src/stories/Announcement/Announcement.stories.tsx b/apps/smithy/src/stories/Announcement/Announcement.stories.tsx index 1e99149e..9f36327b 100644 --- a/apps/smithy/src/stories/Announcement/Announcement.stories.tsx +++ b/apps/smithy/src/stories/Announcement/Announcement.stories.tsx @@ -1,4 +1,11 @@ -import { Announcement, Tour, useFlow, useFrigade } from "@frigade/react"; +import { + Announcement, + FrigadeJS, + Provider, + Tour, + useFlow, + useFrigade, +} from "@frigade/react"; import { useEffect } from "react"; export default { @@ -15,6 +22,88 @@ export const Default = { }, }; +// TEMP verification harness for the mobile popup-blocker fix. Uses __readOnly +// + __flowStateOverrides to mock an Announcement whose primary CTA opens a +// URL in a new tab. With the fix in place, the primary button renders as +// `` so mobile browsers +// don't block the popup. +const MOCK_FLOW_ID = "flow_mock_link_button"; +const linkButtonFlowOverride = { + [MOCK_FLOW_ID]: { + flowSlug: MOCK_FLOW_ID, + flowName: "Link Button Repro", + flowType: FrigadeJS.FlowType.ANNOUNCEMENT, + data: { + steps: [ + { + id: "step-one", + title: "Payment links are here", + subtitle: "Now you can create an order and send your customers a link to pay through multiplate.", + primaryButton: { + title: "Learn more", + uri: "https://example.com/learn-more", + target: "_blank", + }, + secondaryButton: { title: "Dismiss" }, + $state: { + completed: false, + started: false, + visible: true, + blocked: false, + skipped: false, + }, + }, + ], + }, + $state: { + currentStepId: "step-one", + visible: true, + started: false, + completed: false, + skipped: false, + currentStepIndex: 0, + }, + }, +}; + +export const PrimaryButtonAsLink = { + args: { flowId: MOCK_FLOW_ID, modal: true, dismissible: true }, + decorators: [ + (Story, { args }) => ( + + + + ), + ], +}; + +// Same mock flow, but with a custom navigate handler — should fall back to +// rendering as