// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file useLinkComponent.ts * @input React use, useMemo, createElement, forwardRef, LinkContext, LinkComponentType * @output Exports useLinkComponent hook * @position Hook for resolving the link component in Astryx components * * Resolution order: per-component `as` prop > LinkProvider context > native ``. * * When the resolved component is a custom component (not native ``), * wraps it to pass `to={href}` alongside `href`. This enables compatibility * with routers that use `to` (React Router, TanStack Router) * without requiring an adapter component. * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Link/index.ts * - /packages/core/src/Link/Link.doc.mjs */ import { use, useMemo, createElement } from 'react'; import { LinkContext } from "./LinkContext.js"; import type { LinkComponentType } from "./types.js"; /** * Creates a wrapper component that passes both `href` and `to` props * to the underlying link component. This enables routers that use `to` * (React Router, TanStack Router) to work without an adapter. * * The wrapper is transparent: it forwards refs and all other props unchanged. * Native `` elements ignore the unknown `to` prop harmlessly. */ function createLinkWithTo(Component: LinkComponentType): LinkComponentType { function LinkWithTo({ href, ref, ...rest }: { href?: string; to?: string; ref?: React.Ref; }) { return createElement(Component, { ref, href, to: href, ...rest }); } LinkWithTo.displayName = `LinkWithTo(${typeof Component === 'string' ? Component : Component.displayName || Component.name || 'Component'})`; return LinkWithTo as LinkComponentType; } /** * Resolves the link component to use. * * Priority: `as` prop > `LinkProvider` context > native ``. * * When the resolved component is a custom component (not the native ``), * it is wrapped to receive both `href` and `to` props set to the same value. * This allows `to`-based routers (React Router, TanStack Router) to work * out of the box without a manual adapter. * * @param as - Per-component override. If provided, takes highest priority. * @returns The resolved link component (with `to` injection for custom components). * * @example * ``` * function MyComponent({ as }: { as?: LinkComponentType }) { * const LinkComponent = useLinkComponent(as); * return Click me; * } * ``` */ export function useLinkComponent(as?: LinkComponentType): LinkComponentType { const ctx = use(LinkContext); const resolved = as ?? ctx?.component ?? 'a'; // Memoize the wrapper to maintain referential stability. // The wrapper is only created for custom components (not native ). return useMemo(() => { if (resolved === 'a') { return 'a'; } return createLinkWithTo(resolved); }, [resolved]); }