diff --git a/src/__tests__/tv.test.ts b/src/__tests__/tv.test.ts index 39c9789..68af986 100644 --- a/src/__tests__/tv.test.ts +++ b/src/__tests__/tv.test.ts @@ -789,12 +789,216 @@ describe("Tailwind Variants (TV) - Slots", () => { const {base, cursor} = menu(); - expect(base()).toEqual("flex flex-wrap w-7 h-7 text-xs"); + expect(base()).toEqual("flex flex-wrap"); expect(base({size: "xs"})).toEqual("flex flex-wrap w-7 h-7 text-xs"); expect(base({size: "sm"})).toEqual("flex flex-wrap w-7 h-7 text-xs"); expect(cursor()).toEqual("absolute flex overflow-visible"); }); + test("should not override the default classes when the variant doesn't match - compoundSlots", () => { + const tabs = tv({ + slots: { + base: "inline-flex", + tabList: ["flex"], + tab: ["z-0", "w-full", "px-3", "py-1", "flex", "group", "relative"], + tabContent: ["relative", "z-10", "text-inherit", "whitespace-nowrap"], + cursor: ["absolute", "z-0", "bg-white"], + panel: ["py-3", "px-1", "outline-none"], + }, + variants: { + variant: { + solid: {}, + light: {}, + underlined: {}, + bordered: {}, + }, + color: { + default: {}, + primary: {}, + secondary: {}, + success: {}, + warning: {}, + danger: {}, + }, + size: { + sm: { + tabList: "rounded-md", + tab: "h-7 text-xs rounded-sm", + cursor: "rounded-sm", + }, + md: { + tabList: "rounded-md", + tab: "h-8 text-sm rounded-sm", + cursor: "rounded-sm", + }, + lg: { + tabList: "rounded-lg", + tab: "h-9 text-md rounded-md", + cursor: "rounded-md", + }, + }, + radius: { + none: { + tabList: "rounded-none", + tab: "rounded-none", + cursor: "rounded-none", + }, + sm: { + tabList: "rounded-md", + tab: "rounded-sm", + cursor: "rounded-sm", + }, + md: { + tabList: "rounded-md", + tab: "rounded-sm", + cursor: "rounded-sm", + }, + lg: { + tabList: "rounded-lg", + tab: "rounded-md", + cursor: "rounded-md", + }, + full: { + tabList: "rounded-full", + tab: "rounded-full", + cursor: "rounded-full", + }, + }, + }, + defaultVariants: { + color: "default", + variant: "solid", + size: "md", + }, + compoundSlots: [ + { + variant: "underlined", + slots: ["tab", "tabList", "cursor"], + class: ["rounded-none"], + }, + ], + }); + + const {tab, tabList, cursor} = tabs(); + + expectTv(tab(), [ + "z-0", + "w-full", + "px-3", + "py-1", + "h-8", + "flex", + "group", + "relative", + "text-sm", + "rounded-sm", + ]); + expectTv(tabList(), ["flex", "rounded-md"]); + expectTv(cursor(), ["absolute", "z-0", "bg-white", "rounded-sm"]); + }); + + test("should override the default classes when the variant matches - compoundSlots", () => { + const tabs = tv({ + slots: { + base: "inline-flex", + tabList: ["flex"], + tab: ["z-0", "w-full", "px-3", "py-1", "flex", "group", "relative"], + tabContent: ["relative", "z-10", "text-inherit", "whitespace-nowrap"], + cursor: ["absolute", "z-0", "bg-white"], + panel: ["py-3", "px-1", "outline-none"], + }, + variants: { + variant: { + solid: {}, + light: {}, + underlined: {}, + bordered: {}, + }, + color: { + default: {}, + primary: {}, + secondary: {}, + success: {}, + warning: {}, + danger: {}, + }, + size: { + sm: { + tabList: "rounded-md", + tab: "h-7 text-xs rounded-sm", + cursor: "rounded-sm", + }, + md: { + tabList: "rounded-md", + tab: "h-8 text-sm rounded-sm", + cursor: "rounded-sm", + }, + lg: { + tabList: "rounded-lg", + tab: "h-9 text-md rounded-md", + cursor: "rounded-md", + }, + }, + radius: { + none: { + tabList: "rounded-none", + tab: "rounded-none", + cursor: "rounded-none", + }, + sm: { + tabList: "rounded-md", + tab: "rounded-sm", + cursor: "rounded-sm", + }, + md: { + tabList: "rounded-md", + tab: "rounded-sm", + cursor: "rounded-sm", + }, + lg: { + tabList: "rounded-lg", + tab: "rounded-md", + cursor: "rounded-md", + }, + full: { + tabList: "rounded-full", + tab: "rounded-full", + cursor: "rounded-full", + }, + }, + }, + defaultVariants: { + color: "default", + variant: "solid", + size: "md", + }, + compoundSlots: [ + { + variant: "underlined", + slots: ["tab", "tabList", "cursor"], + class: ["rounded-none"], + }, + ], + }); + + const {tab, tabList, cursor} = tabs({variant: "underlined"}); + + expectTv(tab(), [ + "z-0", + "w-full", + "px-3", + "py-1", + "h-8", + "flex", + "group", + "relative", + "text-sm", + "rounded-none", + ]); + expectTv(tabList(), ["flex", "rounded-none"]); + expectTv(cursor(), ["absolute", "z-0", "bg-white", "rounded-none"]); + }); + test("should support slot level variant overrides - compoundVariants", () => { const menu = tv({ base: "text-3xl", diff --git a/src/index.js b/src/index.js index 5fd9cf3..e0a5082 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,7 @@ import { removeExtraSpaces, flatMergeArrays, flatArray, + isBoolean, } from "./utils.js"; export const defaultConfig = { @@ -347,18 +348,22 @@ export const tv = (options, configProp) => { className: slotClassName, ...slotVariants } of compoundSlots) { - for (const slotName of slots) { - result[slotName] = result[slotName] || []; - result[slotName].push([slotClass, slotClassName]); - } - if (!isEmptyObject(slotVariants)) { let isValid = true; for (const key of Object.keys(slotVariants)) { const completePropsValue = getCompleteProps(key, slotProps)[key]; - if (completePropsValue === undefined || completePropsValue !== slotVariants[key]) { + // if the value is boolean, skip it + if (isBoolean(slotVariants[key])) { + break; + } + + if ( + completePropsValue === undefined || + !slotVariants[key] || + !slotVariants[key].includes(completePropsValue) + ) { isValid = false; break; } @@ -368,6 +373,11 @@ export const tv = (options, configProp) => { continue; } } + + for (const slotName of slots) { + result[slotName] = result[slotName] || []; + result[slotName].push([slotClass, slotClassName]); + } } return result; diff --git a/src/utils.d.ts b/src/utils.d.ts index fbf0d05..cf13d7c 100644 --- a/src/utils.d.ts +++ b/src/utils.d.ts @@ -11,3 +11,5 @@ export declare const mergeObjects: (obj1: unknown, obj2: unknown) => unknown; export declare const removeExtraSpaces: (str: string) => string; export declare const isEqual: (obj1: object, obj2: object) => boolean; + +export declare const isBoolean: (value: unknown) => boolean; diff --git a/src/utils.js b/src/utils.js index 6eb016f..fbfe45b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -6,6 +6,8 @@ export const isEmptyObject = (obj) => export const isEqual = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2); +export const isBoolean = (value) => typeof value === "boolean"; + function flat(arr, target) { arr.forEach(function (el) { if (Array.isArray(el)) flat(el, target);