Skip to content

chore(tailwind): Small refactor for easier changes and ease of understanding #1351

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

Merged
merged 9 commits into from
Mar 12, 2024
Merged
32 changes: 32 additions & 0 deletions packages/tailwind/src/hooks/use-style-inlining.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Creates a style inlining function that converts an element's className into inlined React styles - based on the
* {@link stylePerClass} map - for all classes also.
*
* Also returns residual classes that could not be found on the map.
*/
export function useStyleInlining(
stylePerClass: Record<string, React.CSSProperties>
) {
return (className: string) => {
const classes = className.split(' ');

const residualClasses = [];
let styles: React.CSSProperties = {};

for (const singleClass of classes) {
if (singleClass in stylePerClass) {
styles = {
...styles,
...stylePerClass[singleClass]
};
} else {
residualClasses.push(singleClass);
}
}

return {
styles,
residualClassName: residualClasses.join(' '),
}
}
}
39 changes: 39 additions & 0 deletions packages/tailwind/src/hooks/use-tailwind-styles.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useTailwindStyles } from "./use-tailwind-styles";

describe("useTailwindStyles()", () => {
test("with basic media queries and nested elements", () => {
const node = (
<div className="w-full md:w-[250px] bg-red-500">
<span className="well-hello text-red-100">Well, hello friends!</span>
</div>
);

expect(useTailwindStyles(node, {})).toEqual({
stylePerClassMap: {
"w-full": { width: "100%" },
"bg-red-500": { backgroundColor: "rgb(239,68,68)" },
"text-red-100": { color: "rgb(254,226,226)" },
},
sanitizedMediaQueries: [".md_w-250px {width: 250px!important}\n"],
nonInlinableClasses: ["md:w-[250px]"],
});
});

test.only("with more media queries", () => {
const node = (
<div className="bg-red-200 sm:bg-red-300 md:bg-red-400 lg:bg-red-500" />
);

expect(useTailwindStyles(node, {})).toEqual({
stylePerClassMap: {
"bg-red-200": { backgroundColor: "rgb(254,202,202)" },
},
sanitizedMediaQueries: [
"@media (min-width: 640px) {.sm_bg-red-300 {background-color: rgb(252,165,165)!important}}",
"@media (min-width: 768px) {.md_bg-red-400 {background-color: rgb(248,113,113)!important}}",
"@media (min-width: 1024px) {.lg_bg-red-500 {background-color: rgb(239,68,68)!important}}",
],
nonInlinableClasses: ["sm:bg-red-300", "md:bg-red-400", "lg:bg-red-500"],
});
});
});
58 changes: 58 additions & 0 deletions packages/tailwind/src/hooks/use-tailwind-styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from "react";
import type { TailwindConfig } from "../tailwind";
import { separateMediaQueriesFromCSS } from "../utils/css/media-queries/separate-media-queries-from-css";
import { rulesFor } from "../utils/css/rules-for";
import { quickSafeRenderToString } from "../utils/quick-safe-render-to-string";
import { getCssForMarkup } from "../utils/tailwindcss/get-css-for-markup";
import { useRgbNonSpacedSyntax } from "../utils/compatibility/use-rgb-non-spaced-syntax";
import { cssToJsxStyle } from "../utils/compatibility/css-to-jsx-style";
import { unescapeClass } from "../utils/compatibility/unescape-class";
import { sanitizeRuleSelector } from "../utils/compatibility/sanitize-rule-selector";
import { makeAllRulePropertiesImportant } from "../utils/compatibility/make-all-rule-properties-important";

/**
* Gets all the necessary information from the node and the Tailwind config to be able
* to apply all the Tailwind styles.
*/
export function useTailwindStyles(
node: React.ReactNode,
config: TailwindConfig,
) {
const markup = quickSafeRenderToString(<>{node}</>);
const css = useRgbNonSpacedSyntax(getCssForMarkup(markup, config));

const [cssWithoutMediaQueries, mediaQueries] =
separateMediaQueriesFromCSS(css);

const stylePerClassMap: Record<string, React.CSSProperties> = {};
for (const rule of rulesFor(cssWithoutMediaQueries)) {
const unescapedClass = unescapeClass(rule.selector);
stylePerClassMap[unescapedClass] = cssToJsxStyle(rule.content);
}

const nonInlinableClasses: string[] = [];

const sanitizedMediaQueries = mediaQueries.map((mediaQuery) => {
let sanitizedMediaQuery = mediaQuery;
for (const rule of rulesFor(mediaQuery)) {
nonInlinableClasses.push(unescapeClass(rule.selector));

sanitizedMediaQuery = sanitizedMediaQuery.replace(
rule.value,
rule.value
.replace(rule.selector, sanitizeRuleSelector(rule.selector))
.replace(rule.content, makeAllRulePropertiesImportant(rule.content))
.trim(),
);
}
return sanitizedMediaQuery
.replace(/(\r\n|\r|\n)+/gm, "")
.replace(/\s+/gm, " ");
});

return {
stylePerClassMap,
sanitizedMediaQueries,
nonInlinableClasses,
};
}
Loading