Skip to content

Commit e663f5f

Browse files
authored
Trim css strings and selectors (#8)
* wip: Add trim css selector util * feat: Add css selector trim test case * chores: test passing tests * chores: revert test fail case * feat: Add important test cases * docs: update examples output
1 parent 2d5ae17 commit e663f5f

File tree

7 files changed

+79
-20
lines changed

7 files changed

+79
-20
lines changed

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ or
3030
```bash
3131
yarn add react-style-stringify
3232
```
33+
3334
> [!NOTE]
3435
> This package uses the `CSSProperties` type from `@types/react`.
35-
>
36+
>
3637
> If you're working with TypeScript and don't use React, install [@types/react](https://www.npmjs.com/package/@types/react).
3738
3839
## Usage
@@ -54,7 +55,7 @@ const cssString = stringifyCSSProperties({
5455
padding: 20,
5556
backgroundColor: "teal",
5657
});
57-
// Output: "flex:1; padding:20px; background-color:teal;"
58+
// Output: "flex:1;padding:20px;background-color:teal;"
5859
```
5960

6061
**Inject `!important` into CSS string**
@@ -68,7 +69,7 @@ const importantCssString = stringifyCSSProperties(
6869
},
6970
true
7071
);
71-
// Output: "flex:1!important; padding:20px!important; background-color:teal!important;"
72+
// Output: "flex:1!important;padding:20px!important;background-color:teal!important;"
7273
```
7374

7475
### Convert a `Record<string, CSSProperties>` object
@@ -83,7 +84,7 @@ const cssMapString = stringifyStyleMap({
8384
padding: 10,
8485
},
8586
});
86-
// Output: "p{margin:0; color:teal;} #root ul.my-list > li{padding:10px;}"
87+
// Output: "p{margin:0;color:teal;}#root ul.my-list>li{padding:10px;}"
8788
```
8889

8990
**Inject `!important` into CSS string**
@@ -101,7 +102,7 @@ const importantCssMapString = stringifyStyleMap(
101102
},
102103
true
103104
);
104-
// Output: "p{margin:0!important; color:teal!important;} #root ul.my-list > li{padding:10px!important;}"
105+
// Output: "p{margin:0!important;color:teal!important;}#root ul.my-list>li{padding:10px!important;}"
105106
```
106107

107108
## API

src/__tests__/stringifyCSSProperties.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe("stringifyCSSProperties", () => {
3535
});
3636

3737
it("doesn't change string CSS-value", () => {
38-
const expected = "color:teal; margin:20rem; padding:5px 10px;";
38+
const expected = "color:teal;margin:20rem;padding:5px 10px;";
3939
const actual = stringifyCSSProperties({
4040
color: "teal",
4141
margin: "20rem",
@@ -47,7 +47,7 @@ describe("stringifyCSSProperties", () => {
4747

4848
it("converts CSS-prop name from camel to kebab case", () => {
4949
const expected =
50-
"margin-bottom:20px; background-color:teal; border-radius:30rem; font-family:sans-serif;";
50+
"margin-bottom:20px;background-color:teal;border-radius:30rem;font-family:sans-serif;";
5151
const actual = stringifyCSSProperties({
5252
marginBottom: "20px",
5353
backgroundColor: "teal",
@@ -59,14 +59,14 @@ describe("stringifyCSSProperties", () => {
5959
});
6060

6161
it("adds 'px' to numeric CSS-value", () => {
62-
const expected = "margin:20px; padding:5px;";
62+
const expected = "margin:20px;padding:5px;";
6363
const actual = stringifyCSSProperties({ margin: 20, padding: 5 });
6464

6565
expect(actual).toBe(expected);
6666
});
6767

6868
it("doesn't add 'px' to unitless CSS-prop and '0' CSS-value", () => {
69-
const expected = "z-index:20; flex:1; opacity:0.5; margin:0; padding:0;";
69+
const expected = "z-index:20;flex:1;opacity:0.5;margin:0;padding:0;";
7070
const actual = stringifyCSSProperties({
7171
zIndex: 20,
7272
flex: 1,
@@ -77,4 +77,19 @@ describe("stringifyCSSProperties", () => {
7777

7878
expect(actual).toBe(expected);
7979
});
80+
81+
it("injects the '!important' statement for each style property", () => {
82+
const expected =
83+
"color:teal!important;margin:20rem!important;padding:5px 10px!important;";
84+
const actual = stringifyCSSProperties(
85+
{
86+
color: "teal",
87+
margin: "20rem",
88+
padding: "5px 10px",
89+
},
90+
true
91+
);
92+
93+
expect(actual).toBe(expected);
94+
});
8095
});

src/__tests__/stringifyStyleMap.test.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,34 @@ describe("stringifyStyleMap", () => {
2727
);
2828
});
2929

30-
it("doesn't change CSS-selector string", () => {
30+
it("makes CSS-rules with CSS-selector string and CSS-properties string", () => {
3131
const expected =
32-
"header{color:teal;} .className{color:teal;} body ul > li{color:teal;}";
32+
"header{color:teal;}.className{color:teal;}#root{color:teal;}";
3333
const actual = stringifyStyleMap({
3434
header: cssProperties,
3535
".className": cssProperties,
36-
"body ul > li": cssProperties,
36+
"#root": cssProperties,
3737
});
3838

3939
expect(actual).toBe(expected);
4040
});
4141

42-
// TODO: add reducing feature to stringifyStyleMap
43-
it("doesn't reduce styles for empty cssProperties", () => {
44-
const expected = "header{color:teal;} main{} footer{color:teal;}";
42+
it("trims CSS-selector string properly", () => {
43+
const expected =
44+
".className{color:teal;}#root div h1{color:teal;}#root>ul li{color:teal;}*>p+ul li{color:teal;}div~p.className{color:teal;}";
45+
const actual = stringifyStyleMap({
46+
" .className ": cssProperties,
47+
"#root div h1": cssProperties,
48+
"#root > ul li": cssProperties,
49+
"* > p+ ul li": cssProperties,
50+
"div ~p.className": cssProperties,
51+
});
52+
53+
expect(actual).toBe(expected);
54+
});
55+
56+
it("reduces styles for empty cssProperties", () => {
57+
const expected = "header{color:teal;}footer{color:teal;}";
4558
const actual = stringifyStyleMap({
4659
header: cssProperties,
4760
main: {},
@@ -50,4 +63,18 @@ describe("stringifyStyleMap", () => {
5063

5164
expect(actual).toBe(expected);
5265
});
66+
67+
it("injects the '!important' statement for each style property", () => {
68+
const expected =
69+
"#root{color:teal!important;}.footer{color:teal!important;}";
70+
const actual = stringifyStyleMap(
71+
{
72+
"#root": cssProperties,
73+
".footer": cssProperties,
74+
},
75+
true
76+
);
77+
78+
expect(actual).toBe(expected);
79+
});
5380
});

src/stringifyCSSProperties.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ export function stringifyCSSProperties(
2424
([key, value]) =>
2525
`${camelToKebab(key)}:${applyCssUnits(key, value)}${important};`
2626
)
27-
.join(" ");
27+
.join("");
2828
}

src/stringifyStyleMap.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type StyleMap } from "./types";
22
import { stringifyCSSProperties } from "./stringifyCSSProperties";
3+
import { trimCssSelector } from "./utils";
34

45
/**
56
* Converts a `StyleMap` (a map of CSS selectors to `CSSProperties`) into a string of CSS rules.
@@ -18,8 +19,16 @@ export function stringifyStyleMap(
1819
}
1920

2021
return Object.entries(styleMap)
21-
.map(
22-
([key, value]) => `${key}{${stringifyCSSProperties(value, isImportant)}}`
23-
)
24-
.join(" ");
22+
.reduce<string[]>((result, [key, value]) => {
23+
if (Object.keys(value).length > 0) {
24+
result.push(
25+
`${trimCssSelector(key)}{${stringifyCSSProperties(
26+
value,
27+
isImportant
28+
)}}`
29+
);
30+
}
31+
return result;
32+
}, [])
33+
.join("");
2534
}

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./camelToKebab";
22
export * from "./isUnitless";
33
export * from "./applyCssUnits";
4+
export * from "./trimCssSelector";

src/utils/trimCssSelector.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export function trimCssSelector(selector: string) {
2+
return selector
3+
.replace(/\s*([+~>])\s*/g, "$1")
4+
.replace(/\s{2,}/g, " ")
5+
.trim();
6+
}

0 commit comments

Comments
 (0)