Skip to content

Commit

Permalink
feat(tw-merge): ✨ add tw-merge locally to test next build (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
navin-moorthy authored Oct 7, 2021
1 parent 6e793e2 commit 690a400
Show file tree
Hide file tree
Showing 14 changed files with 1,820 additions and 15 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@
"dependencies": {
"@react-aria/live-announcer": "^3.0.1",
"@renderlesskit/react": "^0.7.1",
"hashlru": "^2.3.0",
"lodash": "^4.17.21",
"reakit": "^1.3.10",
"reakit-system": "^0.15.2",
"reakit-utils": "^0.15.2",
"reakit-warning": "^0.6.2",
"tailwind-merge": "^0.7.0"
"reakit-warning": "^0.6.2"
},
"devDependencies": {
"@babel/cli": "7.15.7",
Expand Down
7 changes: 2 additions & 5 deletions src/checkbox/__keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const USE_CHECKBOX_GROUP_STATE_KEYS = [
] as const;
export const CHECKBOX_GROUP_STATE_KEYS = USE_CHECKBOX_GROUP_STATE_KEYS;
export const CHECKBOX_STATE_KEYS = [
...CHECKBOX_GROUP_STATE_KEYS,
"state",
"size",
"isChecked",
"isIndeterminate",
"isUnchecked",
Expand All @@ -21,10 +21,7 @@ export const USE_CHECKBOX_STATE_KEYS = [
"size",
"value",
] as const;
export const CHECKBOX_DESCRIPTION_KEYS = [
...CHECKBOX_GROUP_STATE_KEYS,
...CHECKBOX_STATE_KEYS,
] as const;
export const CHECKBOX_DESCRIPTION_KEYS = CHECKBOX_STATE_KEYS;
export const CHECKBOX_ICON_KEYS = [
...CHECKBOX_DESCRIPTION_KEYS,
"description",
Expand Down
2 changes: 1 addition & 1 deletion src/utils/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import { cx } from "@chakra-ui/utils";
import { createTailwindMerge } from "tailwind-merge";

import { createTailwindMerge } from "./tailwind-merge";
import { Dict, RenderPropType } from "./types";

export interface CreateContextOptions {
Expand Down
129 changes: 129 additions & 0 deletions src/utils/tailwind-merge/class-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { ClassGroup, ClassGroupId, ClassValidator, Config } from "./types";

export interface ClassPartObject {
nextPart: Record<string, ClassPartObject>;
validators: ClassValidatorObject[];
classGroupId?: ClassGroupId;
}

interface ClassValidatorObject {
classGroupId: ClassGroupId;
validator: ClassValidator;
}

const CLASS_PART_SEPARATOR = "-";

export function createClassUtils(config: Config) {
const classMap = createClassMap(config);

function getClassGroupId(className: string) {
const classParts = className.split(CLASS_PART_SEPARATOR);

// Classes like `-inset-1` produce an empty string as first classPart. We assume that classes for negative values are used correctly and remove it from classParts.
if (classParts[0] === "" && classParts.length !== 1) {
classParts.shift();
}

return getGroupRecursive(classParts, classMap);
}

function getConflictingClassGroupIds(classGroupId: ClassGroupId) {
return config.conflictingClassGroups[classGroupId] || [];
}

return {
getClassGroupId,
getConflictingClassGroupIds,
};
}

function getGroupRecursive(
classParts: string[],
classPartObject: ClassPartObject,
): ClassGroupId | undefined {
if (classParts.length === 0) {
return classPartObject.classGroupId;
}

const currentClassPart = classParts[0]!;
const nextClassPartObject = classPartObject.nextPart[currentClassPart];
const classGroupFromNextClassPart = nextClassPartObject
? getGroupRecursive(classParts.slice(1), nextClassPartObject)
: undefined;

if (classGroupFromNextClassPart) {
return classGroupFromNextClassPart;
}

if (classPartObject.validators.length === 0) {
return undefined;
}

const classRest = classParts.join(CLASS_PART_SEPARATOR);

return classPartObject.validators.find(({ validator }) =>
validator(classRest),
)?.classGroupId;
}

/**
* Exported for testing only
*/
export function createClassMap(config: Config) {
const classMap: ClassPartObject = {
nextPart: {},
validators: [],
};

Object.entries(config.classGroups).forEach(([classGroupId, classGroup]) => {
processClassesRecursively(classGroup, classMap, classGroupId);
});

return classMap;
}

function processClassesRecursively(
classGroup: ClassGroup,
classPartObject: ClassPartObject,
classGroupId: ClassGroupId,
) {
classGroup.forEach(classDefinition => {
if (typeof classDefinition === "string") {
const classPartObjectToEdit =
classDefinition === ""
? classPartObject
: getPart(classPartObject, classDefinition);
classPartObjectToEdit.classGroupId = classGroupId;
} else if (typeof classDefinition === "function") {
classPartObject.validators.push({
validator: classDefinition,
classGroupId,
});
} else {
Object.entries(classDefinition).forEach(([key, classGroup]) => {
processClassesRecursively(
classGroup,
getPart(classPartObject, key),
classGroupId,
);
});
}
});
}

function getPart(classPartObject: ClassPartObject, path: string) {
let currentClassPartObject = classPartObject;

path.split(CLASS_PART_SEPARATOR).forEach(pathPart => {
if (currentClassPartObject.nextPart[pathPart] === undefined) {
currentClassPartObject.nextPart[pathPart] = {
nextPart: {},
validators: [],
};
}

currentClassPartObject = currentClassPartObject.nextPart[pathPart]!;
});

return currentClassPartObject;
}
14 changes: 14 additions & 0 deletions src/utils/tailwind-merge/config-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createClassUtils } from "./class-utils";
import { getLruCache } from "./lru-cache";
import { createPrefixUtils } from "./prefix-utils";
import { Config } from "./types";

export type ConfigUtils = ReturnType<typeof createConfigUtils>;

export function createConfigUtils(config: Config) {
return {
cache: getLruCache<string>(config.cacheSize),
...createPrefixUtils(config),
...createClassUtils(config),
};
}
44 changes: 44 additions & 0 deletions src/utils/tailwind-merge/config-validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const customValueRegex = /^\[(.+)\]$/;
const fractionRegex = /^\d+\/\d+$/;
const stringLengths = new Set(["px", "full", "screen"]);
const lengthUnitRegex =
/\d+(%|px|em|rem|vh|vw|pt|pc|in|cm|mm|cap|ch|ex|lh|rlh|vi|vb|vmin|vmax)/;

export function isLength(classPart: string) {
return (
isCustomLength(classPart) ||
!Number.isNaN(Number(classPart)) ||
stringLengths.has(classPart) ||
fractionRegex.test(classPart)
);
}

export function isCustomLength(classPart: string) {
const customValue = customValueRegex.exec(classPart)?.[1];

if (customValue) {
return (
customValue.startsWith("length:") || lengthUnitRegex.test(customValue)
);
}

return false;
}

export function isInteger(classPart: string) {
const customValue = customValueRegex.exec(classPart)?.[1];

if (customValue) {
return Number.isInteger(Number(customValue));
}

return Number.isInteger(Number(classPart));
}

export function isCustomValue(classPart: string) {
return customValueRegex.test(classPart);
}

export function isAny() {
return true;
}
Loading

1 comment on commit 690a400

@vercel
Copy link

@vercel vercel bot commented on 690a400 Oct 7, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.