diff --git a/packages/shared/src/components/highlights/DigestCTA.tsx b/packages/shared/src/components/highlights/DigestCTA.tsx
index 17d6d09b86c..19b4f2b1962 100644
--- a/packages/shared/src/components/highlights/DigestCTA.tsx
+++ b/packages/shared/src/components/highlights/DigestCTA.tsx
@@ -1,5 +1,6 @@
import type { ReactElement } from 'react';
import React, { useCallback } from 'react';
+import classNames from 'classnames';
import type { ChannelDigestConfiguration } from '../../graphql/highlights';
import type { Source } from '../../graphql/sources';
import { SourceType } from '../../graphql/sources';
@@ -9,6 +10,9 @@ import { useSourceActionsFollow } from '../../hooks/source/useSourceActionsFollo
import { useSourceActionsNotify } from '../../hooks/source/useSourceActionsNotify';
import SourceActionsNotify from '../sources/SourceActions/SourceActionsNotify';
import Link from '../utilities/Link';
+import { HighlightShareButton } from './HighlightShareButton';
+import { ButtonSize } from '../buttons/Button';
+import { ReferralCampaignKey } from '../../lib/referral';
const CTA_HEIGHT = 'h-10';
@@ -22,6 +26,12 @@ const DigestCTASkeleton = (): ReactElement => (
interface DigestCTAProps {
digest: ChannelDigestConfiguration;
displayName: string;
+ /**
+ * Absolute link to this channel's Happening Now tab. Only set when
+ * `share_happening_now` is on, so the control is gated on the prop and the
+ * flag-off DOM is untouched.
+ */
+ shareLink?: string;
}
interface DigestCTAContentProps extends DigestCTAProps {
@@ -31,6 +41,7 @@ interface DigestCTAContentProps extends DigestCTAProps {
const DigestCTAContent = ({
digest,
displayName,
+ shareLink,
source,
}: DigestCTAContentProps): ReactElement => {
const { isAuthReady, isLoggedIn } = useAuthContext();
@@ -61,7 +72,9 @@ const DigestCTAContent = ({
-
+ {/* The share control eats horizontal room, so the copy only gets the
+ shrink + ellipsis treatment when it is actually rendered. */}
+
Get a {digest.frequency} digest of{' '}
@@ -70,7 +83,19 @@ const DigestCTAContent = ({
{' '}
news
-
+ {shareLink && (
+
+ )}
+ {
@@ -87,6 +112,7 @@ const DigestCTAContent = ({
export const DigestCTA = ({
digest,
displayName,
+ shareLink,
}: DigestCTAProps): ReactElement | null => {
const source = digest.source
? {
@@ -104,6 +130,7 @@ export const DigestCTA = ({
);
diff --git a/packages/shared/src/components/highlights/HighlightItem.spec.tsx b/packages/shared/src/components/highlights/HighlightItem.spec.tsx
index 5f1e186989a..095ff48f228 100644
--- a/packages/shared/src/components/highlights/HighlightItem.spec.tsx
+++ b/packages/shared/src/components/highlights/HighlightItem.spec.tsx
@@ -1,10 +1,28 @@
import React from 'react';
-import { render, screen } from '@testing-library/react';
+import {
+ act,
+ fireEvent,
+ render,
+ screen,
+ waitFor,
+} from '@testing-library/react';
+import { QueryClient } from '@tanstack/react-query';
import type { PostHighlightFeed } from '../../graphql/highlights';
import { HighlightItem } from './HighlightItem';
+import { TestBootProvider } from '../../../__tests__/helpers/boot';
+import { useViewSize } from '../../hooks/useViewSize';
+import type { ToastNotification } from '../../hooks/useToastNotification';
+import { TOAST_NOTIF_KEY } from '../../hooks/useToastNotification';
+jest.mock('../../hooks/useViewSize', () => {
+ const actual = jest.requireActual('../../hooks/useViewSize');
+ return { __esModule: true, ...actual, useViewSize: jest.fn() };
+});
+
+const useViewSizeMock = useViewSize as jest.Mock;
const scrollIntoView = jest.fn();
const summary = 'A concise summary for the expanded highlight item.';
+const SHARE_LABEL = 'Share this highlight';
const highlight: PostHighlightFeed = {
id: 'highlight-1',
@@ -27,7 +45,8 @@ beforeAll(() => {
});
beforeEach(() => {
- scrollIntoView.mockClear();
+ jest.clearAllMocks();
+ useViewSizeMock.mockReturnValue(true); // default: laptop
});
describe('HighlightItem', () => {
@@ -46,3 +65,79 @@ describe('HighlightItem', () => {
expect(scrollIntoView).toHaveBeenCalled();
});
});
+
+let client: QueryClient;
+
+const renderWithShare = (showShare: boolean) => {
+ client = new QueryClient();
+ return render(
+
+
+ ,
+ );
+};
+
+describe('HighlightItem share control', () => {
+ it('should not render a share control when the flag is off', () => {
+ renderWithShare(false);
+
+ expect(
+ screen.getByRole('link', { name: /read more/i }),
+ ).toBeInTheDocument();
+ expect(screen.queryByLabelText(SHARE_LABEL)).not.toBeInTheDocument();
+ });
+
+ it('should render exactly one share control per highlight when enabled', () => {
+ renderWithShare(true);
+
+ expect(screen.getAllByLabelText(SHARE_LABEL)).toHaveLength(1);
+ expect(
+ screen.getByRole('link', { name: /read more/i }),
+ ).toBeInTheDocument();
+ });
+
+ it('should copy the post link and show a toast on desktop', async () => {
+ const writeText = jest.fn().mockResolvedValue(undefined);
+ Object.assign(navigator, { clipboard: { writeText } });
+
+ renderWithShare(true);
+
+ await act(async () => {
+ fireEvent.click(screen.getByLabelText(SHARE_LABEL));
+ });
+ await act(async () => {
+ fireEvent.click(await screen.findByText('Copy link'));
+ });
+
+ await waitFor(() =>
+ expect(writeText).toHaveBeenCalledWith('/posts/post-1'),
+ );
+ await waitFor(() =>
+ expect(
+ client.getQueryData(TOAST_NOTIF_KEY)?.message,
+ ).toEqual('✅ Copied link to clipboard'),
+ );
+ });
+
+ it('should open the native share sheet on a single tap on mobile', async () => {
+ useViewSizeMock.mockReturnValue(false);
+ const share = jest.fn().mockResolvedValue(undefined);
+ Object.assign(navigator, { share, maxTouchPoints: 2 });
+
+ renderWithShare(true);
+
+ await act(async () => {
+ fireEvent.click(screen.getByLabelText(SHARE_LABEL));
+ });
+
+ await waitFor(() =>
+ expect(share).toHaveBeenCalledWith({
+ text: `${highlight.headline}\n/posts/post-1`,
+ }),
+ );
+ });
+});
diff --git a/packages/shared/src/components/highlights/HighlightItem.tsx b/packages/shared/src/components/highlights/HighlightItem.tsx
index 4256c618b38..4eb03587638 100644
--- a/packages/shared/src/components/highlights/HighlightItem.tsx
+++ b/packages/shared/src/components/highlights/HighlightItem.tsx
@@ -6,17 +6,23 @@ import { stripHtmlTags } from '../../lib/strings';
import { PostType } from '../../graphql/posts';
import { ArrowIcon } from '../icons/Arrow';
import { IconSize } from '../Icon';
+import { ButtonSize } from '../buttons/Button';
import Link from '../utilities/Link';
import { RelativeTime } from '../utilities/RelativeTime';
+import { HighlightShareButton } from './HighlightShareButton';
+import { ReferralCampaignKey } from '../../lib/referral';
interface HighlightItemProps {
highlight: PostHighlightFeed;
defaultExpanded?: boolean;
+ /** Gated by `share_happening_now`; resolved once by `HighlightsPage`. */
+ showShare?: boolean;
}
export const HighlightItem = ({
highlight,
defaultExpanded = false,
+ showShare = false,
}: HighlightItemProps): ReactElement => {
const [expanded, setExpanded] = useState(defaultExpanded);
const ref = useRef(null);
@@ -52,6 +58,14 @@ export const HighlightItem = ({
return '';
}, [highlight.post]);
+ const readMore = (
+
+
+ Read more
+
+
+ );
+
return (
)}
diff --git a/packages/shared/src/components/highlights/HighlightShareButton.tsx b/packages/shared/src/components/highlights/HighlightShareButton.tsx
new file mode 100644
index 00000000000..4b7c26231e0
--- /dev/null
+++ b/packages/shared/src/components/highlights/HighlightShareButton.tsx
@@ -0,0 +1,68 @@
+import type { ReactElement } from 'react';
+import React, { useCallback } from 'react';
+import { ShareActions } from '../share/ShareActions';
+import type { ButtonSize } from '../buttons/Button';
+import { useLogContext } from '../../contexts/LogContext';
+import { LogEvent, Origin } from '../../lib/log';
+import type { ReferralCampaignKey } from '../../lib/referral';
+import type { ShareProvider } from '../../lib/share';
+
+// Which of the three Happening Now nesting levels the control belongs to. Kept
+// on the log payload so the levels can be compared without three log events.
+export type HighlightShareLevel = 'page' | 'topic' | 'highlight';
+
+export interface HighlightShareButtonProps {
+ link: string;
+ text: string;
+ label: string;
+ level: HighlightShareLevel;
+ targetId: string;
+ cid?: ReferralCampaignKey;
+ buttonSize?: ButtonSize;
+ className?: string;
+}
+
+// Thin logging wrapper around the shared `ShareActions` primitive so all three
+// Happening Now levels emit an identically shaped event. `LogEvent.ShareLog` is
+// the generic share event: a highlight is not a full `Post` here (the feed query
+// only returns id + permalink), so emitting `SharePost` would produce events
+// missing every standard post property.
+export const HighlightShareButton = ({
+ link,
+ text,
+ label,
+ level,
+ targetId,
+ cid,
+ buttonSize,
+ className,
+}: HighlightShareButtonProps): ReactElement => {
+ const { logEvent } = useLogContext();
+
+ const onShare = useCallback(
+ (provider: ShareProvider) => {
+ logEvent({
+ event_name: LogEvent.ShareLog,
+ target_id: targetId,
+ extra: JSON.stringify({
+ origin: Origin.HappeningNow,
+ provider,
+ level,
+ }),
+ });
+ },
+ [logEvent, targetId, level],
+ );
+
+ return (
+
+ );
+};
diff --git a/packages/shared/src/components/highlights/HighlightsPage.spec.tsx b/packages/shared/src/components/highlights/HighlightsPage.spec.tsx
new file mode 100644
index 00000000000..7a3c4bc8830
--- /dev/null
+++ b/packages/shared/src/components/highlights/HighlightsPage.spec.tsx
@@ -0,0 +1,148 @@
+import React from 'react';
+import type { RenderResult } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
+import { QueryClient } from '@tanstack/react-query';
+import { GrowthBook } from '@growthbook/growthbook-react';
+import nock from 'nock';
+import type { NextRouter } from 'next/router';
+import { useRouter } from 'next/router';
+import { HighlightsPage } from './HighlightsPage';
+import { TestBootProvider } from '../../../__tests__/helpers/boot';
+import { mockGraphQL } from '../../../__tests__/helpers/graphql';
+import {
+ HIGHLIGHTS_PAGE_QUERY,
+ MAJOR_HEADLINES_MAX_FIRST,
+ POST_HIGHLIGHTS_FEED_QUERY,
+} from '../../graphql/highlights';
+import {
+ featureSharingVisibility,
+ featureShareHappeningNow,
+} from '../../lib/featureManagement';
+
+const PAGE_SHARE_LABEL = 'Share Happening Now';
+const TOPIC_SHARE_LABEL = 'Share AI agents';
+const ITEM_SHARE_LABEL = 'Share this highlight';
+
+const summary = 'A concise summary for the expanded highlight item.';
+
+const highlightNode = (id: string) => ({
+ id,
+ channel: 'agents',
+ headline: `Headline ${id}`,
+ highlightedAt: '2026-04-05T09:00:00.000Z',
+ significance: 'major',
+ post: {
+ id: `post-${id}`,
+ type: 'article',
+ commentsPermalink: `/posts/post-${id}`,
+ summary,
+ },
+});
+
+const channelConfiguration = {
+ channel: 'agents',
+ displayName: 'AI agents',
+ digest: {
+ frequency: 'daily',
+ source: {
+ id: 'source-1',
+ name: 'AI agents',
+ image: 'https://daily.dev/image.jpg',
+ handle: 'ai-agents',
+ permalink: 'https://daily.dev/sources/ai-agents',
+ },
+ },
+};
+
+const mockPageQuery = () =>
+ mockGraphQL({
+ request: {
+ query: HIGHLIGHTS_PAGE_QUERY,
+ variables: { first: MAJOR_HEADLINES_MAX_FIRST },
+ },
+ result: {
+ data: {
+ majorHeadlines: {
+ pageInfo: { endCursor: null, hasNextPage: false },
+ edges: [{ node: highlightNode('h1') }],
+ },
+ channelConfigurations: [channelConfiguration],
+ },
+ },
+ });
+
+const mockChannelQuery = () =>
+ mockGraphQL({
+ request: {
+ query: POST_HIGHLIGHTS_FEED_QUERY,
+ variables: { channel: 'agents' },
+ },
+ result: {
+ data: { postHighlights: [highlightNode('h1'), highlightNode('h2')] },
+ },
+ });
+
+beforeEach(() => {
+ nock.cleanAll();
+ jest.clearAllMocks();
+ Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
+ configurable: true,
+ value: jest.fn(),
+ });
+});
+
+const getGrowthBook = (enabled: boolean): GrowthBook => {
+ const gb = new GrowthBook();
+ gb.setFeatures({
+ [featureSharingVisibility.id]: { defaultValue: enabled },
+ [featureShareHappeningNow.id]: { defaultValue: enabled },
+ });
+
+ return gb;
+};
+
+const renderChannelTab = (enabled: boolean): RenderResult => {
+ (useRouter as jest.Mock).mockReturnValue({
+ query: { channel: 'agents', highlight: 'h1' },
+ pathname: '/highlights/[channel]',
+ push: jest.fn(),
+ asPath: '/highlights/agents',
+ } as unknown as NextRouter);
+
+ mockPageQuery();
+ mockChannelQuery();
+
+ return render(
+
+
+ ,
+ );
+};
+
+describe('HighlightsPage sharing', () => {
+ it('should render no share controls when the flag is off', async () => {
+ renderChannelTab(false);
+
+ expect(await screen.findByText(summary)).toBeInTheDocument();
+ expect(screen.queryByLabelText(PAGE_SHARE_LABEL)).not.toBeInTheDocument();
+ expect(screen.queryByLabelText(TOPIC_SHARE_LABEL)).not.toBeInTheDocument();
+ expect(screen.queryByLabelText(ITEM_SHARE_LABEL)).not.toBeInTheDocument();
+ });
+
+ it('should render exactly one control per level when enabled', async () => {
+ renderChannelTab(true);
+
+ // Only the route-expanded highlight exposes an item-level control, so the
+ // three levels never stack up into a row of share buttons.
+ expect(await screen.findByText(summary)).toBeInTheDocument();
+ await waitFor(() =>
+ expect(screen.getAllByLabelText(PAGE_SHARE_LABEL)).toHaveLength(1),
+ );
+ expect(screen.getAllByLabelText(TOPIC_SHARE_LABEL)).toHaveLength(1);
+ expect(screen.getAllByLabelText(ITEM_SHARE_LABEL)).toHaveLength(1);
+ });
+});
diff --git a/packages/shared/src/components/highlights/HighlightsPage.tsx b/packages/shared/src/components/highlights/HighlightsPage.tsx
index eb11f9e7566..f26ef3b24b2 100644
--- a/packages/shared/src/components/highlights/HighlightsPage.tsx
+++ b/packages/shared/src/components/highlights/HighlightsPage.tsx
@@ -14,6 +14,13 @@ import {
import { Tab, TabContainer } from '../tabs/TabContainer';
import { DigestCTA } from './DigestCTA';
import { HighlightItem } from './HighlightItem';
+import { HighlightShareButton } from './HighlightShareButton';
+import { useSharingVisibility } from '../../hooks/useSharingVisibility';
+import { useConditionalFeature } from '../../hooks/useConditionalFeature';
+import { featureShareHappeningNow } from '../../lib/featureManagement';
+import { webappUrl } from '../../lib/constants';
+import { ButtonSize } from '../buttons/Button';
+import { ReferralCampaignKey } from '../../lib/referral';
const MAJOR_HEADLINES_LABEL = 'Headlines';
const ALL_HIGHLIGHTS_LABEL = 'All';
@@ -48,12 +55,14 @@ interface HighlightFeedListProps {
highlights: PostHighlightFeed[];
loading: boolean;
expandedId?: string;
+ showShare?: boolean;
}
const HighlightFeedList = ({
highlights,
loading,
expandedId,
+ showShare,
}: HighlightFeedListProps): ReactElement => {
if (loading) {
return (
@@ -80,6 +89,7 @@ const HighlightFeedList = ({
key={highlight.id}
highlight={highlight}
defaultExpanded={highlight.id === expandedId}
+ showShare={showShare}
/>
))}
@@ -90,24 +100,29 @@ const MajorHeadlinesTab = ({
highlights,
loading,
expandedId,
+ showShare,
}: {
highlights: PostHighlightFeed[];
loading: boolean;
expandedId?: string;
+ showShare?: boolean;
}): ReactElement => (
);
const ChannelTab = ({
channel,
expandedId,
+ showShare,
}: {
channel: ChannelConfiguration;
expandedId?: string;
+ showShare?: boolean;
}): ReactElement => {
const { data, isFetching } = useChannelHighlights(channel.channel);
const highlights = data?.postHighlights ?? [];
@@ -116,12 +131,19 @@ const ChannelTab = ({
return (
<>
{channel.digest && (
-
+
)}
>
);
@@ -130,9 +152,11 @@ const ChannelTab = ({
const AllHighlightsTab = ({
active,
expandedId,
+ showShare,
}: {
active: boolean;
expandedId?: string;
+ showShare?: boolean;
}): ReactElement => {
const { data, isFetching } = useQuery({
...postHighlightsFeedQueryOptions(),
@@ -148,6 +172,7 @@ const AllHighlightsTab = ({
highlights={highlights}
loading={isFetching && !data}
expandedId={expandedId}
+ showShare={showShare}
/>
);
};
@@ -171,12 +196,42 @@ export const HighlightsPage = (): ReactElement => {
? ALL_HIGHLIGHTS_LABEL
: channelLabel ?? MAJOR_HEADLINES_LABEL;
+ // One flag evaluation for the whole surface: the per-topic and per-item
+ // controls receive the resolved boolean as a prop so nothing re-evaluates
+ // GrowthBook once per highlight row.
+ const { isEnabled: isSharingVisible } = useSharingVisibility();
+ const { value: isShareHappeningNowEnabled } = useConditionalFeature({
+ feature: featureShareHappeningNow,
+ shouldEvaluate: isSharingVisible,
+ });
+ const showShare = isSharingVisible && isShareHappeningNowEnabled;
+
+ const activePath = (() => {
+ if (isAllTab) {
+ return ALL_HIGHLIGHTS_URL;
+ }
+
+ return channel ? `${HIGHLIGHTS_BASE_URL}/${channel}` : HIGHLIGHTS_BASE_URL;
+ })();
+
return (
Happening Now
+ {showShare && (
+
+ )}
{
highlights={majorHeadlines}
loading={majorLoading}
expandedId={expandedId}
+ showShare={showShare}
/>
,
-
+ ,
...channels.map((ch) => (
{
label={ch.displayName}
url={`${HIGHLIGHTS_BASE_URL}/${ch.channel}`}
>
-
+
)),
]}
diff --git a/packages/shared/src/lib/featureManagement.ts b/packages/shared/src/lib/featureManagement.ts
index 97b38cbdd2b..538a6374dab 100644
--- a/packages/shared/src/lib/featureManagement.ts
+++ b/packages/shared/src/lib/featureManagement.ts
@@ -308,3 +308,12 @@ export const featureSharingVisibility = new Feature(
// high-traffic icon, so it ramps on its own flag to watch share/copy metrics.
// Keep the default `false` (control = existing `LinkIcon`).
export const featureShareCopyIcon = new Feature('share_copy_icon', false);
+
+// Adds share/copy affordances to the Happening Now surface at three levels: the
+// whole page (header), a channel's digest CTA, and an individual expanded
+// highlight. Also gated by the `sharing_visibility` master switch. Keep the
+// default `false` — GrowthBook ramps it.
+export const featureShareHappeningNow = new Feature(
+ 'share_happening_now',
+ false,
+);
diff --git a/packages/storybook/stories/components/HappeningNowShare.stories.tsx b/packages/storybook/stories/components/HappeningNowShare.stories.tsx
new file mode 100644
index 00000000000..51839dd75c4
--- /dev/null
+++ b/packages/storybook/stories/components/HappeningNowShare.stories.tsx
@@ -0,0 +1,208 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import React from 'react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { DigestCTA } from '@dailydotdev/shared/src/components/highlights/DigestCTA';
+import { HighlightItem } from '@dailydotdev/shared/src/components/highlights/HighlightItem';
+import { HighlightShareButton } from '@dailydotdev/shared/src/components/highlights/HighlightShareButton';
+import { ButtonSize } from '@dailydotdev/shared/src/components/buttons/Button';
+import type { PostHighlightFeed } from '@dailydotdev/shared/src/graphql/highlights';
+import { getLogContextStatic } from '@dailydotdev/shared/src/contexts/LogContext';
+import AuthContext from '@dailydotdev/shared/src/contexts/AuthContext';
+import { fn } from 'storybook/test';
+
+const CHANNEL_LINK = 'https://app.daily.dev/highlights/agents';
+const PAGE_LINK = 'https://app.daily.dev/highlights';
+
+const digest = {
+ frequency: 'daily',
+ source: {
+ id: 'source-1',
+ name: 'AI agents',
+ image: 'https://daily.dev/image.jpg',
+ handle: 'ai-agents',
+ permalink: 'https://app.daily.dev/sources/ai-agents',
+ },
+};
+
+const highlight: PostHighlightFeed = {
+ id: 'highlight-1',
+ channel: 'agents',
+ headline: 'Agent frameworks converge on a single tool-calling spec',
+ highlightedAt: new Date().toISOString(),
+ significance: 'major',
+ post: {
+ id: 'post-1',
+ type: 'article',
+ commentsPermalink: 'https://app.daily.dev/posts/post-1',
+ summary:
+ 'The three biggest agent frameworks now emit the same tool-calling payload, which means adapters written for one runtime drop straight into the others.',
+ },
+};
+
+// The Happening Now header is part of `HighlightsPage`, which needs the router
+// and the highlights queries. The header row is reproduced here only so the
+// page-level control can be reviewed next to the other two levels.
+const PageHeader = (): React.ReactElement => (
+
+
+);
+
+const meta: Meta = {
+ title: 'Components/Share/HappeningNow',
+ component: HappeningNowSurface,
+ args: { showShare: true },
+ argTypes: {
+ showShare: {
+ control: 'boolean',
+ description:
+ 'Resolved value of `share_happening_now` (AND the `sharing_visibility` master switch). `HighlightsPage` evaluates it once and threads it down, so these stories take the resolved boolean directly — no GrowthBook mock is involved and the off state is a real control.',
+ },
+ },
+ decorators: [
+ (Story) => {
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false, staleTime: Infinity } },
+ });
+ const LogContext = getLogContextStatic();
+
+ return (
+
+
+ false,
+ }}
+ >
+
+
+
+
+
+
+ );
+ },
+ ],
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+// All three levels at once — page header, channel digest CTA, expanded
+// highlight. Only the expanded highlight exposes an item-level control, so the
+// levels never pile up into a row of share buttons.
+export const AllLevels: Story = {
+ args: { showShare: true },
+};
+
+// True control: the same surface with the resolved flag off. Nothing about the
+// existing markup changes.
+export const FlagOff: Story = {
+ args: { showShare: false },
+};
+
+// Level 1 — the whole Happening Now page.
+export const PageLevel: Story = {
+ render: () => ,
+};
+
+// Level 2 — a single channel/topic, next to the digest subscribe control.
+export const TopicLevel: Story = {
+ render: () => (
+
+ ),
+};
+
+// Level 3 — a single highlight, next to "Read more".
+export const ItemLevel: Story = {
+ render: () => (
+
+ ),
+};
+
+// `ShareActions` switches to a one-tap native share sheet below the laptop
+// breakpoint (it reads the real viewport via `useViewSize`), so narrow the
+// preview pane to exercise it. The frame below only previews the layout at
+// mobile width.
+export const MobileWidth: Story = {
+ args: { showShare: true },
+ render: (args) => (
+