Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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**
Expand All @@ -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<string, CSSProperties>` object
Expand All @@ -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**
Expand All @@ -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
Expand Down
23 changes: 19 additions & 4 deletions src/__tests__/stringifyCSSProperties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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,
Expand All @@ -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);
});
});
39 changes: 33 additions & 6 deletions src/__tests__/stringifyStyleMap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {},
Expand All @@ -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);
});
});
2 changes: 1 addition & 1 deletion src/stringifyCSSProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ export function stringifyCSSProperties(
([key, value]) =>
`${camelToKebab(key)}:${applyCssUnits(key, value)}${important};`
)
.join(" ");
.join("");
}
17 changes: 13 additions & 4 deletions src/stringifyStyleMap.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,8 +19,16 @@ export function stringifyStyleMap(
}

return Object.entries(styleMap)
.map(
([key, value]) => `${key}{${stringifyCSSProperties(value, isImportant)}}`
)
.join(" ");
.reduce<string[]>((result, [key, value]) => {
if (Object.keys(value).length > 0) {
result.push(
`${trimCssSelector(key)}{${stringifyCSSProperties(
value,
isImportant
)}}`
);
}
return result;
}, [])
.join("");
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./camelToKebab";
export * from "./isUnitless";
export * from "./applyCssUnits";
export * from "./trimCssSelector";
6 changes: 6 additions & 0 deletions src/utils/trimCssSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function trimCssSelector(selector: string) {
return selector
.replace(/\s*([+~>])\s*/g, "$1")
.replace(/\s{2,}/g, " ")
.trim();
}