Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | 1x 1x 1x 1x 1x 1x | import type {
RouteLayerOptions,
RouteLocationInput,
RouteMatchType,
RouterLinkResolved,
RouterLinkType
} from '@esmx/router';
import {
type CSSProperties,
createElement,
type ElementType,
forwardRef,
type MouseEvent,
type ReactElement,
type ReactNode,
type Ref,
useCallback,
useMemo
} from 'react';
import { useRouter } from './context';
/**
* Props for RouterLink component.
* Similar to RouterLinkProps from @esmx/router with React-specific additions.
*/
export interface RouterLinkComponentProps {
/** Target route location (required) */
to: RouteLocationInput;
/** Navigation type */
type?: RouterLinkType;
/** @deprecated Use type='replace' instead */
replace?: boolean;
/** Active matching mode */
exact?: RouteMatchType;
/** CSS class for active state */
activeClass?: string;
/** Event(s) that trigger navigation */
event?: string | string[];
/** HTML tag to render */
tag?: string;
/** Layer navigation options */
layerOptions?: RouteLayerOptions;
/** Hook function called before navigation */
beforeNavigate?: (event: Event, eventName: string) => void;
/** Link content */
children?: ReactNode;
/** Additional CSS class name */
className?: string;
/** Inline styles */
style?: CSSProperties;
}
/**
* RouterLink component for declarative navigation.
* Renders an anchor tag with proper navigation behavior and active state management.
* Supports all navigation types: push, replace, pushWindow, replaceWindow, pushLayer.
*
* @param props - Component props
* @param props.to - Target route location
* @param props.type - Navigation type ('push' | 'replace' | 'pushWindow' | 'replaceWindow' | 'pushLayer')
* @param props.exact - Active matching mode ('include' | 'exact' | 'route')
* @param props.activeClass - CSS class for active state
* @param props.event - Event(s) that trigger navigation
* @param props.tag - HTML tag to render (default: 'a')
* @param props.layerOptions - Options for layer navigation
* @param props.beforeNavigate - Callback before navigation
* @param props.children - Link content
*
* @example
* ```tsx
* // Basic navigation
* <RouterLink to="/home">Home</RouterLink>
* <RouterLink to="/about">About</RouterLink>
*
* // With object location
* <RouterLink to={{ path: '/users', query: { page: '1' } }}>
* Users
* </RouterLink>
*
* // Replace navigation (no history entry)
* <RouterLink to="/login" type="replace">Login</RouterLink>
*
* // Open in new window
* <RouterLink to="/external" type="pushWindow">External</RouterLink>
*
* // Custom active class
* <RouterLink
* to="/dashboard"
* activeClass="nav-active"
* exact="exact"
* >
* Dashboard
* </RouterLink>
*
* // Custom tag (button)
* <RouterLink to="/submit" tag="button" className="btn">
* Submit
* </RouterLink>
*
* // With beforeNavigate callback
* <RouterLink
* to="/protected"
* beforeNavigate={(e, eventName) => {
* if (!isAuthenticated) {
* e.preventDefault();
* showLoginModal();
* }
* }}
* >
* Protected Page
* </RouterLink>
* ```
*/
function RouterLinkInner(
props: RouterLinkComponentProps & { [key: string]: any },
ref: Ref<HTMLAnchorElement>
): ReactElement {
const {
to,
type = 'push',
replace,
exact = 'include',
activeClass,
event = 'click',
tag = 'a',
layerOptions,
beforeNavigate,
children,
className,
style,
...rest
} = props;
const router = useRouter();
// Resolve the link using router's built-in resolver
const linkResolved: RouterLinkResolved = useMemo(() => {
return router.resolveLink({
to,
type,
replace,
exact,
activeClass,
event,
tag,
layerOptions,
beforeNavigate
});
}, [
router,
to,
type,
replace,
exact,
activeClass,
event,
tag,
layerOptions,
beforeNavigate
]);
// Handle click event
const handleClick = useCallback(
async (e: MouseEvent<HTMLElement>) => {
// Call beforeNavigate callback if provided
beforeNavigate?.(e as unknown as Event, 'click');
// If default was prevented by beforeNavigate or by modifier keys, skip navigation
if (e.defaultPrevented) return;
// Check for modifier keys - let browser handle these
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
if (e.button !== 0) return;
// Prevent default browser navigation
e.preventDefault();
// Navigate using the resolved link's navigate function
await linkResolved.navigate(e as unknown as Event);
},
[linkResolved, beforeNavigate]
);
// Build class names
const computedClassName = useMemo(() => {
const classes: string[] = [];
if (linkResolved.attributes.class) {
classes.push(linkResolved.attributes.class);
}
if (className) {
classes.push(className);
}
return classes.join(' ') || undefined;
}, [linkResolved.attributes.class, className]);
// Build props for the element
const elementProps = {
ref,
href: linkResolved.attributes.href,
target: linkResolved.attributes.target,
rel: linkResolved.attributes.rel,
className: computedClassName,
style,
onClick: handleClick,
...rest
};
// Render the element with the appropriate tag using createElement
return createElement(tag as ElementType, elementProps, children);
}
// Cast needed to fix TypeScript forwardRef type inference issue
// This is a common pattern when index signatures conflict with forwardRef's prop handling
export const RouterLink = forwardRef(
RouterLinkInner as any
) as React.ForwardRefExoticComponent<
RouterLinkComponentProps & {
[key: string]: any;
} & React.RefAttributes<HTMLAnchorElement>
>;
RouterLink.displayName = 'RouterLink';
|