From 66661e6534b3dacfe3d5538e92c0dbdf4aedfce9 Mon Sep 17 00:00:00 2001 From: Artem Karlov Date: Thu, 24 Oct 2024 00:55:42 +0200 Subject: [PATCH 1/6] wip: Add trim css selector util --- src/__tests__/stringifyCSSProperties.test.ts | 8 +++---- src/__tests__/stringifyStyleMap.test.ts | 25 +++++++++++++++----- src/stringifyCSSProperties.ts | 2 +- src/stringifyStyleMap.ts | 17 +++++++++---- src/utils/index.ts | 1 + src/utils/trimCssSelector.ts | 6 +++++ 6 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 src/utils/trimCssSelector.ts diff --git a/src/__tests__/stringifyCSSProperties.test.ts b/src/__tests__/stringifyCSSProperties.test.ts index 97db107..7490d9c 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, diff --git a/src/__tests__/stringifyStyleMap.test.ts b/src/__tests__/stringifyStyleMap.test.ts index 7a85ba9..0b9e70d 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 = + // "header{color:teal;}.className{color:teal;}#root{color:teal;}"; + // const actual = stringifyStyleMap({ + // header: cssProperties, + // ".className": cssProperties, + // "#root": cssProperties, + // "article *:first-child": cssProperties, + // "h1.title": 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: {}, 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(); +} From 8b3c8c04a5d60b1931638a8ab976534722ea2cc3 Mon Sep 17 00:00:00 2001 From: Artem Karlov Date: Thu, 24 Oct 2024 20:08:32 +0200 Subject: [PATCH 2/6] feat: Add css selector trim test case --- src/__tests__/stringifyStyleMap.test.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/__tests__/stringifyStyleMap.test.ts b/src/__tests__/stringifyStyleMap.test.ts index 0b9e70d..9c6c941 100644 --- a/src/__tests__/stringifyStyleMap.test.ts +++ b/src/__tests__/stringifyStyleMap.test.ts @@ -39,19 +39,19 @@ describe("stringifyStyleMap", () => { expect(actual).toBe(expected); }); - // it("trims CSS-selector string properly", () => { - // const expected = - // "header{color:teal;}.className{color:teal;}#root{color:teal;}"; - // const actual = stringifyStyleMap({ - // header: cssProperties, - // ".className": cssProperties, - // "#root": cssProperties, - // "article *:first-child": cssProperties, - // "h1.title": cssProperties, - // }); + 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); - // }); + expect(actual).toBe(expected); + }); it("reduces styles for empty cssProperties", () => { const expected = "header{color:teal;}footer{color:teal;}"; From 053b947cfd5feae854c509b47dbe55dca3210718 Mon Sep 17 00:00:00 2001 From: Artem Karlov Date: Thu, 24 Oct 2024 20:10:38 +0200 Subject: [PATCH 3/6] chores: test passing tests --- src/__tests__/stringifyStyleMap.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/stringifyStyleMap.test.ts b/src/__tests__/stringifyStyleMap.test.ts index 9c6c941..c8a3077 100644 --- a/src/__tests__/stringifyStyleMap.test.ts +++ b/src/__tests__/stringifyStyleMap.test.ts @@ -41,7 +41,7 @@ describe("stringifyStyleMap", () => { 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;}"; + ".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, From 14c53e973cdbffee5a746bf949cca840f4820f64 Mon Sep 17 00:00:00 2001 From: Artem Karlov Date: Thu, 24 Oct 2024 20:14:31 +0200 Subject: [PATCH 4/6] chores: revert test fail case --- src/__tests__/stringifyStyleMap.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/stringifyStyleMap.test.ts b/src/__tests__/stringifyStyleMap.test.ts index c8a3077..9c6c941 100644 --- a/src/__tests__/stringifyStyleMap.test.ts +++ b/src/__tests__/stringifyStyleMap.test.ts @@ -41,7 +41,7 @@ describe("stringifyStyleMap", () => { 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;} "; + ".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, From 423f0ccb8d01632421181441e6e8bea7dfcdc96f Mon Sep 17 00:00:00 2001 From: Artem Karlov Date: Thu, 24 Oct 2024 20:24:16 +0200 Subject: [PATCH 5/6] feat: Add important test cases --- src/__tests__/stringifyCSSProperties.test.ts | 15 +++++++++++++++ src/__tests__/stringifyStyleMap.test.ts | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/__tests__/stringifyCSSProperties.test.ts b/src/__tests__/stringifyCSSProperties.test.ts index 7490d9c..92859b7 100644 --- a/src/__tests__/stringifyCSSProperties.test.ts +++ b/src/__tests__/stringifyCSSProperties.test.ts @@ -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 9c6c941..1f87ba4 100644 --- a/src/__tests__/stringifyStyleMap.test.ts +++ b/src/__tests__/stringifyStyleMap.test.ts @@ -63,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); + }); }); From c68f753ce3d6eaa516b82b2facc3ff177567ad70 Mon Sep 17 00:00:00 2001 From: Artem Karlov Date: Thu, 24 Oct 2024 20:33:16 +0200 Subject: [PATCH 6/6] docs: update examples output --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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