Skip to content

Feat: add rulesVariants feature #176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 324 additions & 2 deletions packages/css/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import type {
PropTarget,
PropVars,
Serializable,
VariantStringMap
VariantStringMap,
RecipeStyleRule,
SlotRecipeDefinition,
SlotRecipeResult
} from "./types";
import {
mapValues,
Expand Down Expand Up @@ -208,7 +211,7 @@ function processPropObject<Target extends PropTarget>(
}

function processCompoundStyle(
style: ComplexCSSRule | string,
style: RecipeStyleRule,
debugId: string | undefined,
index: number
): string {
Expand All @@ -217,6 +220,38 @@ function processCompoundStyle(
: css(style, getDebugName(debugId, `compound_${index}`));
}

export function rulesVariants<Slots extends SlotRecipeDefinition>(
slots: Slots,
debugId?: string
): SlotRecipeResult<Slots> {
const result: Partial<SlotRecipeResult<Slots>> = {};

Object.entries(slots).forEach(([slotName, slotConfig]) => {
const slotDebugId = debugId ? `${debugId}_${slotName}` : slotName;
result[slotName as keyof Slots] = rules(
{
base: slotConfig.base,
variants: slotConfig.variants,
toggles: slotConfig.toggles,
...slotConfig
} as PatternOptions<
Slots[typeof slotName]["variants"],
Slots[typeof slotName]["toggles"],
undefined
>,
slotDebugId
) as RuntimeFn<
ConditionalVariants<
Slots[typeof slotName]["variants"],
Slots[typeof slotName]["toggles"]
>,
ComplexPropDefinitions<PropTarget>
>;
});

return result as SlotRecipeResult<Slots>;
}
Comment on lines +223 to +253
Copy link
Contributor

@black7375 black7375 Apr 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a Mapping feature to maintain the same usability as cssVariant.


// == Tests ====================================================================
// Ignore errors when compiling to CommonJS.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down Expand Up @@ -883,4 +918,291 @@ if (import.meta.vitest) {
});
});
});

describe.concurrent("rulesVariants()", () => {
setFileScope("test");

it("empty slots", () => {
const result = rulesVariants(
{
text: {
fontWeight: "bold",
variants: {
color: {
main: { color: "#0078e5" },
sub: { color: "#fff7ed" }
},
size: {
large: { fontSize: "24px" },
medium: { fontSize: "18px" },
small: { fontSize: "12px" }
}
},
toggles: {
accent: { textDecoration: "underline" }
}
},
image: {
width: "100%",
border: "1px solid #ccc",
borderRadius: "5px",
variants: {
style: {
thumbnail: {
width: "50px"
},
detail: {
width: "80%",
marginBottom: "10px"
}
}
}
}
},
"contents"
);

expect(result.text()).toMatch(className("contents_text"));
expect(result.image()).toMatch(className("contents_image"));
});

it("multiple slot combinations", () => {
const contents = rulesVariants(
{
text: {
fontWeight: "bold",
variants: {
color: {
main: { color: "#0078e5" },
sub: { color: "#fff7ed" }
},
size: {
large: { fontSize: "24px" },
medium: { fontSize: "18px" },
small: { fontSize: "12px" }
}
},
toggles: {
accent: { textDecoration: "underline" }
}
},
image: {
width: "100%",
border: "1px solid #ccc",
borderRadius: "5px",
variants: {
style: {
thumbnail: {
width: "50px"
},
detail: {
width: "80%",
marginBottom: "10px"
}
}
}
}
},
"contents"
);

// Test case 1
expect(
contents.text([
"accent",
{
color: "sub",
size: "medium"
}
])
).toMatch(
/^contents_text__[a-zA-Z0-9]+ contents_text_accent_true__[a-zA-Z0-9]+ contents_text_color_sub__[a-zA-Z0-9]+ contents_text_size_medium__[a-zA-Z0-9]+$/
);

// Test case 2
expect(
contents.image({
style: "thumbnail"
})
).toMatch(
/^contents_image__[a-zA-Z0-9]+ contents_image_style_thumbnail__[a-zA-Z0-9]+$/
);
});

it("slot variants", () => {
const result = rulesVariants(
{
text: {
fontWeight: "bold",
variants: {
color: {
main: { color: "#0078e5" },
sub: { color: "#fff7ed" }
},
size: {
large: { fontSize: "24px" },
medium: { fontSize: "18px" },
small: { fontSize: "12px" }
}
},
toggles: {
accent: { textDecoration: "underline" }
}
},
image: {
width: "100%",
border: "1px solid #ccc",
borderRadius: "5px",
variants: {
style: {
thumbnail: {
width: "50px"
},
detail: {
width: "80%",
marginBottom: "10px"
}
}
}
}
},
"contents"
);

expect(result.text()).toMatch(className("contents_text"));
expect(result.image()).toMatch(className("contents_image"));
});

it("slot toggles", () => {
const result = rulesVariants(
{
text: {
fontWeight: "bold",
variants: {
color: {
main: { color: "#0078e5" },
sub: { color: "#fff7ed" }
},
size: {
large: { fontSize: "24px" },
medium: { fontSize: "18px" },
small: { fontSize: "12px" }
}
},
toggles: {
accent: { textDecoration: "underline" }
}
},
image: {
width: "100%",
border: "1px solid #ccc",
borderRadius: "5px",
variants: {
style: {
thumbnail: {
width: "50px"
},
detail: {
width: "80%",
marginBottom: "10px"
}
}
}
}
},
"contents"
);

expect(result.text()).toMatch(className("contents_text"));
expect(result.image()).toMatch(className("contents_image"));
});

it("slot defaultVariants", () => {
const result = rulesVariants(
{
text: {
fontWeight: "bold",
variants: {
color: {
main: { color: "#0078e5" },
sub: { color: "#fff7ed" }
},
size: {
large: { fontSize: "24px" },
medium: { fontSize: "18px" },
small: { fontSize: "12px" }
}
},
toggles: {
accent: { textDecoration: "underline" }
}
},
image: {
width: "100%",
border: "1px solid #ccc",
borderRadius: "5px",
variants: {
style: {
thumbnail: {
width: "50px"
},
detail: {
width: "80%",
marginBottom: "10px"
}
}
}
}
},
"contents"
);

expect(result.text()).toMatch(className("contents_text"));
expect(result.image()).toMatch(className("contents_image"));
});

it("slot compoundVariants", () => {
const result = rulesVariants(
{
text: {
fontWeight: "bold",
variants: {
color: {
main: { color: "#0078e5" },
sub: { color: "#fff7ed" }
},
size: {
large: { fontSize: "24px" },
medium: { fontSize: "18px" },
small: { fontSize: "12px" }
}
},
toggles: {
accent: { textDecoration: "underline" }
}
},
image: {
width: "100%",
border: "1px solid #ccc",
borderRadius: "5px",
variants: {
style: {
thumbnail: {
width: "50px"
},
detail: {
width: "80%",
marginBottom: "10px"
}
}
}
}
},
"contents"
);

expect(result.text()).toMatch(className("contents_text"));
expect(result.image()).toMatch(className("contents_image"));
});
});
}
Loading
Loading