Advanced Usage

Advanced Usage

Nested Component Structure

Recast excels at managing complex React component structures, offering a significant advantage over other styling utilities like cva (opens in a new tab), tv (opens in a new tab), and stitches (opens in a new tab). Let's explore how Recast handles nested components using a slider example based on Radix UI (opens in a new tab).

Example: Slider Component

Here's how you can create a slider component with independently styled elements:

import { cn } from "../../utils/cn.js";
import * as RadixSliderPrimitive from "@radix-ui/react-slider";
import { RecastWithClassNameProps } from "@rpxl/recast";
import React, { forwardRef } from "react";
 
type Props = React.ComponentPropsWithoutRef<typeof RadixSliderPrimitive.Root> &
  RecastWithClassNameProps<{
    root: string;
    track: string;
    range: string;
    thumb: string;
  }>;
 
const Slider = forwardRef<
  React.ElementRef<typeof RadixSliderPrimitive.Root>,
  Props
>(({ className, cls, ...props }, ref) => {
  return (
    <RadixSliderPrimitive.Root
      ref={ref}
      className={cn(cls?.root, className)}
      {...props}
    >
      <RadixSliderPrimitive.Track className={cls?.track}>
        <RadixSliderPrimitive.Range className={cls?.range} />
      </RadixSliderPrimitive.Track>
      <RadixSliderPrimitive.Thumb className={cls?.thumb} />
    </RadixSliderPrimitive.Root>
  );
});

Now you can apply styles to nested elements when wrapping your component with Recast:

export const Slider = recast(SliderPrimitive, {
  base: {
    root: "relative flex w-full touch-none select-none items-center",
    track: "relative h-1.5 w-full grow overflow-hidden rounded-full bg-black",
    range: "bg-primary absolute h-full",
    thumb: "block h-4 w-4 rounded-full border border-black/50 bg-white shadow",
  },
  // Other Recast theme props can be added here
});

TypeScript Integration

Recast is designed to work seamlessly with TypeScript. When defining your component props, you can use the RecastWithClassNameProps type to ensure type safety for your nested class names:

import { RecastWithClassNameProps } from "@rpxl/recast";
 
type Props = YourComponentProps &
  RecastWithClassNameProps<{
    root: string;
    childElement: string;
    // ... other nested elements
  }>;

This approach provides excellent TypeScript support, enabling autocompletion and type checking for your themed components.

Wrapping a Recast Component

Sometimes you might want to wrap a Recast component to customize its behavior. Here's an example of wrapping a ScrollArea component to infer the variant based on the orientation prop:

"use client";
import { recast } from "@rpxl/recast";
import { ScrollAreaPrimitive } from "@/components/primitives/scroll-area.tsx";
import { forwardRef } from "react";
 
const BaseScrollArea = recast(ScrollAreaPrimitive, {
  defaults: { variants: { variant: "vertical" } },
  base: {
    root: "relative overflow-hidden",
    viewport: "h-full w-full rounded-[inherit]",
    thumb: "bg-border relative flex-1 rounded-full",
    scrollbar: "flex touch-none select-none transition-colors",
  },
  variants: {
    variant: {
      vertical: {
        scrollbar: "h-full w-2.5 border-l border-l-transparent p-[1px]",
      },
      horizontal: {
        scrollbar: "h-2.5 flex-col border-t border-t-transparent p-[1px]",
      },
    },
  },
});
 
type Props = Omit<
  React.ComponentPropsWithoutRef<typeof BaseScrollArea>,
  "variant"
>;
 
export const ScrollArea = forwardRef<
  React.ElementRef<typeof BaseScrollArea>,
  Props
>(({ ...props }, ref) => {
  return <BaseScrollArea ref={ref} variant={props.orientation} {...props} />;
});
 
ScrollArea.displayName = "ScrollArea";

This approach allows you to create more intuitive APIs for your components while still leveraging Recast's theming capabilities.