diff --git a/README.md b/README.md index 22d340b..b8e3761 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,10 @@ or ```bash yarn add react-style-stringify ``` + > [!NOTE] > This package uses the `CSSProperties` type from `@types/react`. -> +> > If you're working with TypeScript and don't use React, install [@types/react](https://www.npmjs.com/package/@types/react). ## Usage @@ -54,7 +55,7 @@ const cssString = stringifyCSSProperties({ padding: 20, backgroundColor: "teal", }); -// Output: "flex:1; padding:20px; background-color:teal;" +// Output: "flex:1;padding:20px;background-color:teal;" ``` **Inject `!important` into CSS string** @@ -68,7 +69,7 @@ const importantCssString = stringifyCSSProperties( }, true ); -// Output: "flex:1!important; padding:20px!important; background-color:teal!important;" +// Output: "flex:1!important;padding:20px!important;background-color:teal!important;" ``` ### Convert a `Record` object @@ -83,7 +84,7 @@ const cssMapString = stringifyStyleMap({ padding: 10, }, }); -// Output: "p{margin:0; color:teal;} #root ul.my-list > li{padding:10px;}" +// Output: "p{margin:0;color:teal;}#root ul.my-list>li{padding:10px;}" ``` **Inject `!important` into CSS string** @@ -101,7 +102,7 @@ const importantCssMapString = stringifyStyleMap( }, true ); -// Output: "p{margin:0!important; color:teal!important;} #root ul.my-list > li{padding:10px!important;}" +// Output: "p{margin:0!important;color:teal!important;}#root ul.my-list>li{padding:10px!important;}" ``` ## API diff --git a/src/__tests__/stringifyCSSProperties.test.ts b/src/__tests__/stringifyCSSProperties.test.ts index 97db107..92859b7 100644 --- a/src/__tests__/stringifyCSSProperties.test.ts +++ b/src/__tests__/stringifyCSSProperties.test.ts @@ -35,7 +35,7 @@ describe("stringifyCSSProperties", () => { }); it("doesn't change string CSS-value", () => { - const expected = "color:teal; margin:20rem; padding:5px 10px;"; + const expected = "color:teal;margin:20rem;padding:5px 10px;"; const actual = stringifyCSSProperties({ color: "teal", margin: "20rem", @@ -47,7 +47,7 @@ describe("stringifyCSSProperties", () => { it("converts CSS-prop name from camel to kebab case", () => { const expected = - "margin-bottom:20px; background-color:teal; border-radius:30rem; font-family:sans-serif;"; + "margin-bottom:20px;background-color:teal;border-radius:30rem;font-family:sans-serif;"; const actual = stringifyCSSProperties({ marginBottom: "20px", backgroundColor: "teal", @@ -59,14 +59,14 @@ describe("stringifyCSSProperties", () => { }); it("adds 'px' to numeric CSS-value", () => { - const expected = "margin:20px; padding:5px;"; + const expected = "margin:20px;padding:5px;"; const actual = stringifyCSSProperties({ margin: 20, padding: 5 }); expect(actual).toBe(expected); }); it("doesn't add 'px' to unitless CSS-prop and '0' CSS-value", () => { - const expected = "z-index:20; flex:1; opacity:0.5; margin:0; padding:0;"; + const expected = "z-index:20;flex:1;opacity:0.5;margin:0;padding:0;"; const actual = stringifyCSSProperties({ zIndex: 20, flex: 1, @@ -77,4 +77,19 @@ describe("stringifyCSSProperties", () => { expect(actual).toBe(expected); }); + + it("injects the '!important' statement for each style property", () => { + const expected = + "color:teal!important;margin:20rem!important;padding:5px 10px!important;"; + const actual = stringifyCSSProperties( + { + color: "teal", + margin: "20rem", + padding: "5px 10px", + }, + true + ); + + expect(actual).toBe(expected); + }); }); diff --git a/src/__tests__/stringifyStyleMap.test.ts b/src/__tests__/stringifyStyleMap.test.ts index 7a85ba9..1f87ba4 100644 --- a/src/__tests__/stringifyStyleMap.test.ts +++ b/src/__tests__/stringifyStyleMap.test.ts @@ -27,21 +27,34 @@ describe("stringifyStyleMap", () => { ); }); - it("doesn't change CSS-selector string", () => { + it("makes CSS-rules with CSS-selector string and CSS-properties string", () => { const expected = - "header{color:teal;} .className{color:teal;} body ul > li{color:teal;}"; + "header{color:teal;}.className{color:teal;}#root{color:teal;}"; const actual = stringifyStyleMap({ header: cssProperties, ".className": cssProperties, - "body ul > li": cssProperties, + "#root": cssProperties, }); expect(actual).toBe(expected); }); - // TODO: add reducing feature to stringifyStyleMap - it("doesn't reduce styles for empty cssProperties", () => { - const expected = "header{color:teal;} main{} footer{color:teal;}"; + it("trims CSS-selector string properly", () => { + const expected = + ".className{color:teal;}#root div h1{color:teal;}#root>ul li{color:teal;}*>p+ul li{color:teal;}div~p.className{color:teal;}"; + const actual = stringifyStyleMap({ + " .className ": cssProperties, + "#root div h1": cssProperties, + "#root > ul li": cssProperties, + "* > p+ ul li": cssProperties, + "div ~p.className": cssProperties, + }); + + expect(actual).toBe(expected); + }); + + it("reduces styles for empty cssProperties", () => { + const expected = "header{color:teal;}footer{color:teal;}"; const actual = stringifyStyleMap({ header: cssProperties, main: {}, @@ -50,4 +63,18 @@ describe("stringifyStyleMap", () => { expect(actual).toBe(expected); }); + + it("injects the '!important' statement for each style property", () => { + const expected = + "#root{color:teal!important;}.footer{color:teal!important;}"; + const actual = stringifyStyleMap( + { + "#root": cssProperties, + ".footer": cssProperties, + }, + true + ); + + expect(actual).toBe(expected); + }); }); diff --git a/src/stringifyCSSProperties.ts b/src/stringifyCSSProperties.ts index 1d56c5c..456d261 100644 --- a/src/stringifyCSSProperties.ts +++ b/src/stringifyCSSProperties.ts @@ -24,5 +24,5 @@ export function stringifyCSSProperties( ([key, value]) => `${camelToKebab(key)}:${applyCssUnits(key, value)}${important};` ) - .join(" "); + .join(""); } diff --git a/src/stringifyStyleMap.ts b/src/stringifyStyleMap.ts index 307ddf9..452967a 100644 --- a/src/stringifyStyleMap.ts +++ b/src/stringifyStyleMap.ts @@ -1,5 +1,6 @@ import { type StyleMap } from "./types"; import { stringifyCSSProperties } from "./stringifyCSSProperties"; +import { trimCssSelector } from "./utils"; /** * Converts a `StyleMap` (a map of CSS selectors to `CSSProperties`) into a string of CSS rules. @@ -18,8 +19,16 @@ export function stringifyStyleMap( } return Object.entries(styleMap) - .map( - ([key, value]) => `${key}{${stringifyCSSProperties(value, isImportant)}}` - ) - .join(" "); + .reduce((result, [key, value]) => { + if (Object.keys(value).length > 0) { + result.push( + `${trimCssSelector(key)}{${stringifyCSSProperties( + value, + isImportant + )}}` + ); + } + return result; + }, []) + .join(""); } diff --git a/src/utils/index.ts b/src/utils/index.ts index 7042799..63b3aaa 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ export * from "./camelToKebab"; export * from "./isUnitless"; export * from "./applyCssUnits"; +export * from "./trimCssSelector"; diff --git a/src/utils/trimCssSelector.ts b/src/utils/trimCssSelector.ts new file mode 100644 index 0000000..9a203e4 --- /dev/null +++ b/src/utils/trimCssSelector.ts @@ -0,0 +1,6 @@ +export function trimCssSelector(selector: string) { + return selector + .replace(/\s*([+~>])\s*/g, "$1") + .replace(/\s{2,}/g, " ") + .trim(); +}