Skip to content
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

Support extensions in gwMerge #111

Merged
merged 5 commits into from
Sep 23, 2024
Merged
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
83 changes: 49 additions & 34 deletions lib/gw-merge.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,58 @@
import { extendTailwindMerge } from "tailwind-merge";

const twMerge = extendTailwindMerge({
prefix: "gw-",
});
// Code adapted from https://github.com/dcastil/tailwind-merge/issues/412

const gwPrefix = "gw-";
const regex = /^(-?)(gw\w*-)(-?)(.*)/;

/**
* So what we want to do is let consuming applications give us
* regular tailwind classes, and we need to merge them with classes
* with a prefix of "gw-". We can do this by extending the tailwind-merge
* function and passing in the prefix we want to use.
* Resolves conflicts between bare or groundwork-prefixed tailwind classNames.
*
* This function accepts any number of bare or groundwork-prefixed tailwind
* classNames as strings and returns a className string with conflicts removed
* and prefixes retained. The last-provided classNames take precedence.
*
* Groundwork prefixes are required to be in the format of "gw{\w*}-", e.g.
* "gw-", "gww-", "gwtest-", "gwmap-", etc.
*
* NOTE: This function currently utilizes the experimentalParseClassName
* key in extendTailwindMerge, which is an experimental feature. Updates
* to tailwind-merge may introduce breaking changes. If gwMerge breaks,
* that's probably the first thing to check.
*/
function gwMerge(...classes) {
// allow space delimited lists of classes
// map across and add the prefix to any we need to,
// make sure to keep track of which ones are user supplied
const extOverrides = {};
classes = classes
.join(" ")
.split(" ")
.map((cls) => {
if (cls && !cls.startsWith("gw-")) {
const newCls = `gw-${cls}`;
extOverrides[cls] = newCls;
return newCls;
}
return cls;
});
const gwMerge = extendTailwindMerge({
prefix: gwPrefix,
experimentalParseClassName: ({ className, parseClassName }) => {
const parsed = parseClassName(className);
const match = parsed.baseClassName.match(regex);
const isNegative = checkNegative(parsed.baseClassName, match);
const baseClass = match ? match[4] : parsed.baseClassName;
const unsignedClass = baseClass.startsWith("-")
? baseClass.substring(1)
: baseClass;
const prefixedClass = gwPrefix + unsignedClass;
const finalClass = (isNegative ? "-" : "") + prefixedClass;

// now we can run tailwind-merge
let classString = twMerge(classes);

// replace any of the user supplied classes that still exist
// this feels like it could open up bugs, but I'm not sure
Object.keys(extOverrides).forEach((cls) => {
classString = classString.replace(extOverrides[cls], cls);
});
return {
...parsed,
baseClassName: finalClass,
};
},
});

return classString;
}
/**
* Determine if a tailwind className is negative.
* @param {string} className The base className from tailwind.
* @param {RegExpMatchArray|null} match The match object from the groundwind prefix regex.
* @returns {boolean} True when a negative tailwind className, false otherwise.
*/
const checkNegative = (className, match) => {
if (!match) {
return className.startsWith("-");
} else {
return match[1] || match[3];
}
};

export default gwMerge;
export { gwMerge };
export default gwMerge;
37 changes: 37 additions & 0 deletions lib/gw-merge.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect, it } from "vitest";
import gwMerge from "./gw-merge";

it("merges multiple groundwork prefixes", () => {
const gw = "gw-mx-2 gw-px-2";
const gww = "gww-px-4 gww-text-black-500";
const gwfoo = "gwfoo-text-white-500";
const user = "text-gray-500 text-sm";
expect(gwMerge(gw, gww, gwfoo, user)).toBe(
"gw-mx-2 gww-px-4 text-gray-500 text-sm"
);
});

it("merges negative class with positive class", () => {
const p2 = "-gww-p-2";
expect(gwMerge("gww-p-1 gww-bg-green-100", p2)).toEqual(
"gww-bg-green-100 -gww-p-2"
);
});

it("merges positive class with negative class", () => {
const p2 = "gww-p-2";
expect(gwMerge("-gww-p-1 gww-bg-green-100", p2)).toEqual(
"gww-bg-green-100 gww-p-2"
);
});

it("merges negative class with negative class", () => {
const p2 = "-gww-p-2";
expect(gwMerge("-gww-p-1 gww-bg-green-100", p2)).toEqual(
"gww-bg-green-100 -gww-p-2"
);
});

it("merges both negative formats", () => {
expect(gwMerge("gw--mx-4 -gw-my-4 -gww-mx-2 my--2")).toBe("-gww-mx-2 my--2");
});
Loading
Loading