Skip to content

Instantly share code, notes, and snippets.

@malerba118
Created March 1, 2024 17:22
Show Gist options
  • Save malerba118/102395407e90fbcdeb77a49b67499add to your computer and use it in GitHub Desktop.
Save malerba118/102395407e90fbcdeb77a49b67499add to your computer and use it in GitHub Desktop.
props-tunnel-shadcn-cva
"use client";
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils";
function createPropsTunnel<TunneledProps extends Record<string, any>>(
defaultProps: Required<TunneledProps>
) {
const PropsContext = React.createContext<TunneledProps | null>(null);
function withProvider<T extends React.ComponentType<any>>(
Component: T
): React.FC<React.ComponentProps<T> & TunneledProps> {
const WithProvider = React.forwardRef<React.ComponentProps<T>, any>(
(props, ref) => {
// Split tunneled props from the rest
const otherProps = Object.keys(props).reduce((acc, key) => {
if (key in defaultProps) {
// ignore
} else {
acc[key] = props[key];
}
return acc;
}, {} as React.ComponentProps<T>);
const tunneledProps = Object.keys(defaultProps).reduce((acc, key) => {
acc[key as keyof TunneledProps] = props[key] || defaultProps[key];
return acc;
}, {} as TunneledProps);
return (
<PropsContext.Provider value={tunneledProps}>
<Component {...(otherProps as any)} ref={ref} />
</PropsContext.Provider>
);
}
);
WithProvider.displayName = `WithProvider(${
Component.displayName || Component.name || "Component"
})`;
return WithProvider as React.FC<React.ComponentProps<T> & TunneledProps>;
}
function useProps(): TunneledProps {
const context = React.useContext(PropsContext);
if (!context) {
throw new Error(
"useProps must be used within a component wrapped by withProvider"
);
}
return context;
}
return { withProvider, useProps };
}
const tunnel = createPropsTunnel<{
variant?: "underlined" | "solid";
size?: "sm" | "lg";
}>({ variant: "underlined", size: "sm" });
const Tabs = tunnel.withProvider(TabsPrimitive.Root);
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => {
const tunneledProps = tunnel.useProps();
return (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
);
});
TabsList.displayName = TabsPrimitive.List.displayName;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment