'use client';
/**
* React-Router (v7) adapter — bridges our framework-agnostic router hooks
* to `react-router` so SPA navigations flow through the data router
* (`createBrowserRouter`) instead of the bare History API fallback.
*
* USAGE:
* Mount once INSIDE the router context — i.e. inside an element that
* `RouterProvider` renders (a layout-route element / `` parent),
* NOT around `` itself (that subtree has no router context,
* so `useNavigate` would throw):
*
* ```tsx
* import { ReactRouterProvider } from '@djangocfg/ui-core/adapters/react-router';
*
* function Shell() {
* return (
*
*
*
* );
* }
* ```
*
* Without this provider, our hooks fall back to the History API adapter —
* which mutates `window.history` directly and bypasses React-Router's
* `` swap, so programmatic `useNavigate` / `` clicks desync
* from the router. Mount it in any Vite / React-Router SPA.
*
* PEER DEPENDENCY:
* `react-router` is an OPTIONAL peer of @djangocfg/ui-core. The base package
* never imports from `react-router`. This sub-path entry does — so it only
* loads when the consumer explicitly imports `/adapters/react-router`.
* Next.js / non-RR consumers: don't import this file and `react-router` is
* never resolved.
*/
import { forwardRef, useMemo, type ReactNode } from 'react';
import { Link as RouterLink, useNavigate } from 'react-router';
import {
RouterAdapterProvider,
type RouterAdapter,
type RouterLocation,
} from '../adapter';
import {
LinkProvider,
type LinkComponent,
type LinkComponentProps,
} from '../../../components/navigation/link';
const SSR_LOCATION: RouterLocation = Object.freeze({
pathname: '/',
search: '',
hash: '',
});
export interface ReactRouterAdapterProps {
children: ReactNode;
}
/**
* Wraps a subtree so all `@djangocfg/ui-core/hooks` router calls go through
* React-Router's data router. `back`/`forward` use the numeric-delta overload
* of `useNavigate`; `getLocation` reads `window.location` (SSR-guarded).
*
* Must be rendered inside the router context (under `RouterProvider`), since
* `useNavigate` requires it.
*/
export function ReactRouterAdapter({ children }: ReactRouterAdapterProps) {
const navigate = useNavigate();
const adapter = useMemo(
() => ({
push(url) {
navigate(url);
},
replace(url) {
navigate(url, { replace: true });
},
back() {
navigate(-1);
},
forward() {
navigate(1);
},
getLocation() {
if (typeof window === 'undefined') return SSR_LOCATION;
return {
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash,
};
},
}),
[navigate]
);
return (
{children}
);
}
/**
* Maps our agnostic Link API → react-router `` props. Lives at module
* scope so the component identity is stable (avoids tree remounts on every
* render). `href` → `to`; `replace` is forwarded; `prefetch` (a Next-shaped
* boolean) and `scroll` have no `react-router` equivalent and are dropped.
*/
const ReactRouterLinkAdapter: LinkComponent = forwardRef(
function ReactRouterLinkAdapter({ href, replace, scroll: _scroll, prefetch: _prefetch, children, ...rest }, ref) {
return (
{children}
);
}
);
export interface ReactRouterLinkProviderProps {
children: ReactNode;
}
/**
* Wires `` from `@djangocfg/ui-core/components` to react-router's
* ``. Mount alongside `ReactRouterAdapter` (inside the router context)
* so every ui-core `` drives client-side routing.
*/
export function ReactRouterLinkProvider({ children }: ReactRouterLinkProviderProps) {
return {children};
}
export interface ReactRouterProviderProps {
children: ReactNode;
}
/**
* Convenience composition of `ReactRouterAdapter` (navigation backend) and
* `ReactRouterLinkProvider` (link component) — mount this ONE component inside
* the router context to wire ui-core's whole navigation seam to React-Router.
*/
export function ReactRouterProvider({ children }: ReactRouterProviderProps) {
return (
{children}
);
}