import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../../utils.js"; import "./marker.css"; export const markerVariants = cva("cfui-marker", { variants: { variant: { default: "cfui-marker--default", border: "cfui-marker--border", separator: "cfui-marker--separator", }, }, defaultVariants: { variant: "default", }, }); export type MarkerVariant = "default" | "border" | "separator"; export interface MarkerProps extends React.HTMLAttributes, VariantProps { variant?: MarkerVariant; asChild?: boolean; href?: string; type?: "button" | "submit" | "reset"; render?: | React.ReactElement | (( props: React.HTMLAttributes, state: { variant: MarkerVariant } ) => React.ReactNode); } function composeRefs(...refs: (React.Ref | undefined | null)[]) { return (node: T | null) => { for (const ref of refs) { if (!ref) continue; if (typeof ref === "function") { ref(node); } else if (typeof ref === "object" && "current" in ref) { (ref as React.MutableRefObject).current = node; } } }; } function mergeElementProps( ours: Record, theirs: Record, forwardedRef: React.Ref ) { const theirsRef = (theirs as any)?.ref ?? (theirs as any)?.props?.ref; const merged: Record = { ...theirs, ...ours }; const allKeys = new Set([...Object.keys(ours), ...Object.keys(theirs)]); for (const key of allKeys) { if (/^on[A-Z]/.test(key)) { const ourHandler = ours[key]; const theirHandler = theirs[key]; if (typeof ourHandler === "function" && typeof theirHandler === "function") { merged[key] = (event: any) => { theirHandler(event); if (!event?.defaultPrevented) { ourHandler(event); } }; } else { merged[key] = ourHandler ?? theirHandler; } } } merged.className = cn(ours.className, theirs.className); if (ours.style || theirs.style) { merged.style = { ...theirs.style, ...ours.style }; } if (ours.href !== undefined || theirs.href !== undefined) { merged.href = ours.href ?? theirs.href; } if (ours.type !== undefined || theirs.type !== undefined) { merged.type = ours.type ?? theirs.type; } merged.ref = composeRefs(forwardedRef, theirsRef); merged.children = ours.children ?? theirs.children; return merged; } export const Marker = React.forwardRef( ( { variant = "default", asChild = false, render, href, type, role, className, children, ...props }, ref ) => { // Native role is caller-owned; labeled separators keep meaningful text exposed rather than assigning role=separator. const resolvedRole = role; const mergedProps = { ref, role: resolvedRole, "data-variant": variant, className: cn(markerVariants({ variant }), className), href, type, ...props, }; if (asChild) { return {children}; } if (typeof render === "function") { return render(mergedProps, { variant }); } if (React.isValidElement(render)) { const renderProps = (render.props || {}) as Record; const merged = mergeElementProps(mergedProps, renderProps, ref); return React.cloneElement(render as React.ReactElement, merged); } if (href) { return ( )} > {children} ); } if (type) { return ( ); } return (
)}> {children}
); } ); Marker.displayName = "Marker"; /* ========================================================================== MarkerIcon Decorative MarkerIcon is aria-hidden ========================================================================== */ export interface MarkerIconProps extends React.HTMLAttributes { asChild?: boolean; } export const MarkerIcon = React.forwardRef( ({ asChild = false, className, children, ...props }, ref) => { const Component = asChild ? Slot : "span"; return ( ); } ); MarkerIcon.displayName = "MarkerIcon"; /* ========================================================================== MarkerContent ========================================================================== */ export interface MarkerContentProps extends React.HTMLAttributes { asChild?: boolean; } export const MarkerContent = React.forwardRef< HTMLSpanElement, MarkerContentProps >(({ asChild = false, className, children, ...props }, ref) => { const Component = asChild ? Slot : "span"; return ( {children} ); }); MarkerContent.displayName = "MarkerContent";