diff --git a/.changeset/cold-spoons-wonder.md b/.changeset/cold-spoons-wonder.md new file mode 100644 index 0000000..9fa2ad7 --- /dev/null +++ b/.changeset/cold-spoons-wonder.md @@ -0,0 +1,6 @@ +--- +"@rpxl/recast-tailwind-plugin": patch +"@rpxl/sandbox": patch +--- + +Add nested variant resolution diff --git a/packages/recast-tailwind-plugin/src/__tests__/index.test.ts b/packages/recast-tailwind-plugin/src/__tests__/index.test.ts index 69de2cb..2eaa5d4 100644 --- a/packages/recast-tailwind-plugin/src/__tests__/index.test.ts +++ b/packages/recast-tailwind-plugin/src/__tests__/index.test.ts @@ -81,7 +81,7 @@ describe("Recast Tailwind Plugin", () => { vi.resetAllMocks(); }); afterEach(() => { - vi.clearAllMocks(); + vi.restoreAllMocks(); }); describe("DEBUG", () => { @@ -161,6 +161,74 @@ describe("Recast Tailwind Plugin", () => { const { result, pluginResult } = await run(config); console.log(pluginResult); }); + + + + it("should handle nested variant structures", async () => { + let config = { + content: [ + { + raw: js` + import { cn } from "@/utils/cn"; + import { RecastWithClassNameProps, recast } from "@rpxl/recast"; + import React, { forwardRef } from "react"; + + type Props = React.HTMLAttributes & { + as?: React.ElementType< + React.DetailedHTMLProps, HTMLElement> + >; + } & RecastWithClassNameProps<{ + root: string; + inner: string; + }>; + + const Component = forwardRef( + ({ rcx, children, as: Tag = "section", className, ...props }, ref) => { + return ( + +
{children}
+
+ ); + }, + ); + + Component.displayName = "SectionWrapper"; + + export const SectionWrapper = recast(Component, { + defaults: { variants: { width: "md" } }, + breakpoints: ["md"], + base: { + root: "flex w-full justify-center overflow-hidden", + inner: "relative w-full px-4", + }, + variants: { + width: { + sm: { root: "bg-blue-500", inner: "max-w-4xl" }, + md: { root: "bg-red-500", inner: "max-w-6xl" }, + lg: { root: "bg-green-500", inner: "max-w-7xl" }, + }, + }, + }); + + `, + }, + ], + corePlugins: { preflight: false }, + theme: { + screens: { + md: DEFAULT_SCREEN_SIZE, + }, + }, + }; + + const { result, pluginResult } = await run(config); + expect(result.css).toContain(".md\\:bg-blue-500"); + expect(result.css).toContain(".md\\:max-w-4xl"); + expect(result.css).toContain(".md\\:bg-red-500"); + expect(result.css).toContain(".md\\:max-w-6xl"); + expect(result.css).toContain(".md\\:bg-green-500"); + expect(result.css).toContain(".md\\:max-w-7xl"); + }); }); describe("Basic functionality", () => { @@ -436,6 +504,8 @@ describe("Recast Tailwind Plugin", () => { ) ); }); + + }); describe("getFilePatterns", () => { diff --git a/packages/recast-tailwind-plugin/src/index.ts b/packages/recast-tailwind-plugin/src/index.ts index abcc354..228607a 100644 --- a/packages/recast-tailwind-plugin/src/index.ts +++ b/packages/recast-tailwind-plugin/src/index.ts @@ -219,23 +219,48 @@ export function generateSafelist( Object.entries(components).forEach(([componentName, component]) => { const breakpoints = component.breakpoints || []; + // Check for undefined breakpoints + breakpoints.forEach((breakpoint) => { + if (!screens[breakpoint]) { + console.warn( + `Breakpoint "${breakpoint}" is not defined in Tailwind config.` + ); + } + }); + + // Add base classes + if (component.base) { + addToSafelist(safelist, component.base); + breakpoints.forEach((breakpoint) => { + if (screens[breakpoint] && component.base) { + addToSafelist(safelist, component.base, breakpoint); + } + }); + } + if (component.variants) { Object.entries(component.variants).forEach( ([variantName, variantOptions]) => { Object.entries(variantOptions).forEach(([optionName, classes]) => { - // Add classes without breakpoint - addToSafelist(safelist, classes); - - // Add classes with breakpoints - breakpoints.forEach((breakpoint) => { - if (screens[breakpoint]) { - addToSafelist(safelist, classes, breakpoint); - } else { - console.warn( - `Warning: Breakpoint "${breakpoint}" is not defined in Tailwind config.` - ); - } - }); + if (typeof classes === "object" && classes !== null) { + Object.entries(classes).forEach(([subKey, subClasses]) => { + if (subClasses) { + addToSafelist(safelist, subClasses); + breakpoints.forEach((breakpoint) => { + if (screens[breakpoint]) { + addToSafelist(safelist, subClasses, breakpoint); + } + }); + } + }); + } else if (classes) { + addToSafelist(safelist, classes); + breakpoints.forEach((breakpoint) => { + if (screens[breakpoint]) { + addToSafelist(safelist, classes, breakpoint); + } + }); + } }); } ); @@ -253,10 +278,23 @@ export function generateSafelist( */ export function addToSafelist( safelist: Set, - classes: string | string[], + classes: string | string[] | Record, prefix: string = "" ) { - const classList = Array.isArray(classes) ? classes : classes.split(" "); + let classList: string[]; + + if (typeof classes === "string") { + classList = classes.split(" "); + } else if (Array.isArray(classes)) { + classList = classes; + } else if (typeof classes === "object" && classes !== null) { + classList = Object.values(classes).flatMap((value) => + typeof value === "string" ? value.split(" ") : [] + ); + } else { + console.warn("Invalid classes input:", classes); + return; + } classList.forEach((cls) => { const safelistClass = prefix ? `${prefix}:${cls}` : cls; diff --git a/packages/recast-tailwind-plugin/src/types.ts b/packages/recast-tailwind-plugin/src/types.ts index dd5309c..f855eba 100644 --- a/packages/recast-tailwind-plugin/src/types.ts +++ b/packages/recast-tailwind-plugin/src/types.ts @@ -5,8 +5,8 @@ import type { PluginAPI } from "tailwindcss/types/config"; * Interface for the Recast Tailwind Plugin */ export interface RecastComponent { - base?: string | string[]; - variants?: Record>; + base?: string | Record; + variants?: Record>>; breakpoints?: string[]; } diff --git a/packages/sandbox/app/page.tsx b/packages/sandbox/app/page.tsx index 86b1f49..a05371c 100644 --- a/packages/sandbox/app/page.tsx +++ b/packages/sandbox/app/page.tsx @@ -3,24 +3,16 @@ import { Button } from "./components/button"; export default function Page() { return (
- + */} - - -
- This text is bold, but font-weight will be unset on hover. -
- -
- This text is italic and red, but both will be unset on large screens. -
+ {/* */}
); } diff --git a/packages/sandbox/types/global.d.ts b/packages/sandbox/types/global.d.ts index 32ac00f..419b901 100644 --- a/packages/sandbox/types/global.d.ts +++ b/packages/sandbox/types/global.d.ts @@ -9,7 +9,8 @@ import { breakpoints } from "../tailwind.config"; * Type definition for user breakpoints * @typedef {RecastBreakpoints} UserBreakpoints */ -type BreakpointKeys = keyof typeof breakpoints; +// type BreakpointKeys = keyof typeof breakpoints; +type BreakpointKeys = "sm" | "md" | "lg" | "xl" | "2xl"; /** * Module augmentation for @rpxl/recast