Skip to content

Commit

Permalink
Add nested variant resolution (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
rodleviton authored Sep 22, 2024
1 parent b97015d commit 292a33b
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 31 deletions.
6 changes: 6 additions & 0 deletions .changeset/cold-spoons-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rpxl/recast-tailwind-plugin": patch
"@rpxl/sandbox": patch
---

Add nested variant resolution
72 changes: 71 additions & 1 deletion packages/recast-tailwind-plugin/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe("Recast Tailwind Plugin", () => {
vi.resetAllMocks();
});
afterEach(() => {
vi.clearAllMocks();
vi.restoreAllMocks();
});

describe("DEBUG", () => {
Expand Down Expand Up @@ -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<HTMLElement> & {
as?: React.ElementType<
React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
>;
} & RecastWithClassNameProps<{
root: string;
inner: string;
}>;
const Component = forwardRef<HTMLElement, Props>(
({ rcx, children, as: Tag = "section", className, ...props }, ref) => {
return (
<Tag className={cn(rcx?.root, className)} ref={ref} {...props}>
<div className={rcx?.inner}>{children}</div>
</Tag>
);
},
);
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", () => {
Expand Down Expand Up @@ -436,6 +504,8 @@ describe("Recast Tailwind Plugin", () => {
)
);
});


});

describe("getFilePatterns", () => {
Expand Down
68 changes: 53 additions & 15 deletions packages/recast-tailwind-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
}
});
}
);
Expand All @@ -253,10 +278,23 @@ export function generateSafelist(
*/
export function addToSafelist(
safelist: Set<string>,
classes: string | string[],
classes: string | string[] | Record<string, string>,
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;
Expand Down
4 changes: 2 additions & 2 deletions packages/recast-tailwind-plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Record<string, string | string[]>>;
base?: string | Record<string, string>;
variants?: Record<string, Record<string, string | Record<string, string>>>;
breakpoints?: string[];
}

Expand Down
16 changes: 4 additions & 12 deletions packages/sandbox/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,16 @@ import { Button } from "./components/button";
export default function Page() {
return (
<div className="p-8 flex flex-col gap-12 justify-center items-center w-full min-h-screen">
<Button size="lg" variant="secondary">
{/* <Button size="lg" variant="secondary">
Sandbox
</Button>
</Button> */}
<Button
variant={{ default: "primary", md: "secondary", lg: "tertiary" }}
variant={{ default: "primary", "2xl": "secondary" }}
size={{ default: "sm", md: "md", "2xl": "lg" }}
>
Yo
</Button>
<Button size={{ default: "sm", md: "lg" }}>Kevin</Button>

<div className="font-bold hover:unset:font-bold">
This text is bold, but font-weight will be unset on hover.
</div>

<div className="italic underline lg:unset:italic lg:unset:underline">
This text is italic and red, but both will be unset on large screens.
</div>
{/* <Button size={{ default: "sm", md: "lg" }}>Kevin</Button> */}
</div>
);
}
3 changes: 2 additions & 1 deletion packages/sandbox/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { breakpoints } from "../tailwind.config";
* Type definition for user breakpoints
* @typedef {RecastBreakpoints<typeof breakpoints>} UserBreakpoints
*/
type BreakpointKeys = keyof typeof breakpoints;
// type BreakpointKeys = keyof typeof breakpoints;
type BreakpointKeys = "sm" | "md" | "lg" | "xl" | "2xl";

/**
* Module augmentation for @rpxl/recast
Expand Down

0 comments on commit 292a33b

Please sign in to comment.