diff --git a/example/src/common/Pattern.svelte b/example/src/common/Pattern.svelte
index f1e0e3b..43e462f 100644
--- a/example/src/common/Pattern.svelte
+++ b/example/src/common/Pattern.svelte
@@ -1,10 +1,14 @@
diff --git a/src/consts.ts b/src/consts.ts
index 1b0bb70..58e9467 100644
--- a/src/consts.ts
+++ b/src/consts.ts
@@ -4,6 +4,7 @@ export const LINE_WIDTHS = [
0.5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16,
];
export const GRID_CELL_SIZE: number[] = [...Array(128)].map((_, i) => i + 1);
+export const SPACING: number[] = [...Array(128)].map((_, i) => i + 1);
export const OFFSETS: number[] = [...Array(128 + 16)].map((_, i) => i + 1);
export const DEFAULT_OPTS: IResolvedOpts = {
diff --git a/src/index.ts b/src/index.ts
index 285d3e5..1e6fa48 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,6 @@
import plugin from "tailwindcss/plugin";
-import { GRID_CELL_SIZE, LINE_WIDTHS, OFFSETS } from "./consts";
+import { GRID_CELL_SIZE, LINE_WIDTHS, OFFSETS, SPACING } from "./consts";
+import { arrToTwConfig } from "./lib/arrToTwConfig";
import { generateCellSizes, matchCellSize } from "./lib/cellSizes";
import {
generateLineColors,
@@ -8,9 +9,10 @@ import {
} from "./lib/line";
import { generateOffsets, matchOffsets } from "./lib/offsets";
import { resolveOptions } from "./lib/resolveOptions";
+import { generateSpacing, matchSpacing } from "./lib/spacing";
import { generateGridClass } from "./patterns/grid";
+import { generateHatchingClass, genreateHatchingDirection } from "./patterns/hatching";
import type { IOptions } from "./types";
-import { arrToTwConfig } from "./lib/arrToTwConfig";
export default plugin.withOptions(
(options) => (api) => {
@@ -18,15 +20,25 @@ export default plugin.withOptions(
const opts = resolveOptions(options);
addUtilities([
+ // Patterns
generateGridClass(e(`${opts.prefix}pattern-grid`)),
+ generateHatchingClass(e(`${opts.prefix}pattern-hatching`)),
+ generateHatchingClass(e(`${opts.prefix}pattern-cross-hatching`), {
+ isCrossHatch: true,
+ }),
+
+ // Configs
generateLineWidths(api, opts),
generateCellSizes(api, opts),
+ generateSpacing(api, opts),
+ genreateHatchingDirection(api, opts),
generateLineColors(api, opts),
generateOffsets(api, opts),
]);
matchUtilities({
...matchCellSize(api, opts),
+ ...matchSpacing(api, opts),
...matchLineWidthsAndColors(api, opts),
...matchOffsets(api, opts),
});
@@ -35,6 +47,7 @@ export default plugin.withOptions(
theme: {
bgPatternLineWidth: arrToTwConfig(LINE_WIDTHS),
bgPatternCellSize: arrToTwConfig(GRID_CELL_SIZE),
+ bgPatternSpacing: arrToTwConfig(SPACING),
bgPatternOffsets: arrToTwConfig(OFFSETS, "px"),
},
}),
diff --git a/src/lib/spacing.ts b/src/lib/spacing.ts
new file mode 100644
index 0000000..dd64de1
--- /dev/null
+++ b/src/lib/spacing.ts
@@ -0,0 +1,27 @@
+import type { CSSRuleObject, PluginAPI } from "tailwindcss/types/config";
+import type { IOptions } from "../types";
+
+export const generateSpacing = (
+ api: PluginAPI,
+ opts: IOptions,
+): CSSRuleObject => {
+ const spacing = api.theme("bgPatternSpacing");
+ const styles: Record = {};
+ if (spacing) {
+ for (const key in spacing) {
+ const value = spacing[key];
+ styles[`.${api.e(`${opts.prefix}pattern-spacing-${key}`)}`] = {
+ "--tw-spacing": `${value} /* px */`,
+ } as unknown as CSSRuleObject;
+ }
+ }
+ return styles as CSSRuleObject;
+};
+
+export const matchSpacing = (api: PluginAPI, opts: IOptions) => ({
+ [api.e(`${opts.prefix}pattern-spacing`)]: (value: string) => {
+ return {
+ "--tw-spacing": `${value} /* px */`,
+ } as unknown as CSSRuleObject;
+ }
+});
diff --git a/src/patterns/hatching.ts b/src/patterns/hatching.ts
new file mode 100644
index 0000000..22ff01a
--- /dev/null
+++ b/src/patterns/hatching.ts
@@ -0,0 +1,102 @@
+import type { CSSRuleObject, PluginAPI } from "tailwindcss/types/config";
+import type { IOptions } from "../types";
+
+interface IOpts {
+ lineSize: number;
+ spacing: number;
+ offsetX: number;
+ offsetY: number;
+ isCrossHatch: boolean;
+ isRightLeaning: boolean;
+ lineColor: string;
+}
+
+export const generateHatchingClass = (
+ className: string,
+ _opts?: Partial,
+): CSSRuleObject => {
+ const opts: IOpts = {
+ lineSize: 1,
+ lineColor: "#ffffff",
+ spacing: 12,
+ offsetX: 0,
+ offsetY: 0,
+ isCrossHatch: false,
+ isRightLeaning: false,
+ ..._opts,
+ };
+
+ let bgImageCode = `linear-gradient(
+ var(--tw-hatching-angle),
+ transparent var(--tw-1st-start-stop),
+ var(--tw-line-color) var(--tw-1st-start-stop),
+ var(--tw-line-color) var(--tw-1st-end-stop),
+ transparent var(--tw-1st-end-stop),
+ transparent 100%
+ ),
+ linear-gradient(
+ var(--tw-hatching-angle),
+ transparent var(--tw-2ed-start-stop),
+ var(--tw-line-color) var(--tw-2ed-start-stop),
+ var(--tw-line-color) var(--tw-2ed-end-stop),
+ transparent var(--tw-2ed-end-stop),
+ transparent 100%
+ )
+ `;
+
+ if (opts.isCrossHatch) {
+ bgImageCode += `, linear-gradient(
+ calc(var(--tw-hatching-angle) * -1),
+ transparent var(--tw-1st-start-stop),
+ var(--tw-line-color) var(--tw-1st-start-stop),
+ var(--tw-line-color) var(--tw-1st-end-stop),
+ transparent var(--tw-1st-end-stop),
+ transparent 100%
+ ),
+ linear-gradient(
+ calc(var(--tw-hatching-angle) * -1),
+ transparent var(--tw-2ed-start-stop),
+ var(--tw-line-color) var(--tw-2ed-start-stop),
+ var(--tw-line-color) var(--tw-2ed-end-stop),
+ transparent var(--tw-2ed-end-stop),
+ transparent 100%
+ )`;
+ }
+
+ return {
+ [`.${className}`]: {
+ "--tw-line-size": opts.lineSize.toString(),
+ "--tw-spacing": opts.spacing.toString(),
+ "--tw-offset-x": `${opts.offsetX * -1}px`,
+ "--tw-offset-y": `${opts.offsetY * -1}px`,
+ "--tw-hatching-angle": `${opts.isRightLeaning ? -45 : 45}deg`,
+ "--tw-line-color": opts.lineColor,
+
+ "--tw-unit": "calc((var(--tw-line-size) + var(--tw-spacing)) * 2)",
+ "--tw-line-stop":
+ "calc((var(--tw-line-size) / var(--tw-unit) * 100%) / 2)",
+ "--tw-1st-start-stop": "calc(75% - var(--tw-line-stop))",
+ "--tw-1st-end-stop": "calc(75% + var(--tw-line-stop))",
+ "--tw-2ed-start-stop": "calc(25% - var(--tw-line-stop))",
+ "--tw-2ed-end-stop": "calc(25% + var(--tw-line-stop))",
+ backgroundImage: bgImageCode,
+ backgroundSize: "calc(var(--tw-unit) * 1px) calc(var(--tw-unit) * 1px)",
+ backgroundPosition:
+ "calc(var(--tw-spacing) * -0.5px + var(--tw-offset-x)) var(--tw-offset-y)",
+ },
+ };
+};
+
+export const genreateHatchingDirection = (
+ api: PluginAPI,
+ opts: IOptions,
+): CSSRuleObject => {
+ return {
+ [`.${api.e(`${opts.prefix}pattern-hatching-left-to-right`)}`]: {
+ "--tw-hatching-angle": "45deg",
+ },
+ [`.${api.e(`${opts.prefix}pattern-hatching-right-to-left`)}`]: {
+ "--tw-hatching-angle": "-45deg",
+ },
+ } as unknown as CSSRuleObject;
+};
diff --git a/tests/index.test.ts b/tests/config.test.ts
similarity index 75%
rename from tests/index.test.ts
rename to tests/config.test.ts
index 2493266..357e9d3 100644
--- a/tests/index.test.ts
+++ b/tests/config.test.ts
@@ -1,5 +1,5 @@
import { describe, test } from "vitest";
-import { DEFAULT_OPTS, LINE_WIDTHS, OFFSETS } from "../src/consts";
+import { DEFAULT_OPTS, LINE_WIDTHS, OFFSETS, SPACING } from "../src/consts";
import { css, expectCssToBe, generateTwCss } from "./utils";
describe("line widths", () => {
@@ -24,6 +24,31 @@ describe("line widths", () => {
});
});
+describe("spacing", () => {
+ test(`spacing ${SPACING[0]}-${SPACING[SPACING.length - 1]}`, async () => {
+ const expected: string[] = [];
+ const classes: string[] = [];
+ for (const value of SPACING) {
+ classes.push(`${DEFAULT_OPTS.prefix}pattern-spacing-${value}`);
+ expected.push(
+ css`.${DEFAULT_OPTS.prefix}pattern-spacing-${value} {
+ --tw-spacing: ${value} /* px */
+ }`,
+ );
+ }
+ expectCssToBe(await generateTwCss(classes.join(" ")), expected.join(""));
+ });
+
+ test("custom spacing", async () => {
+ expectCssToBe(
+ await generateTwCss(`${DEFAULT_OPTS.prefix}pattern-spacing-[321]`),
+ css`.${DEFAULT_OPTS.prefix}pattern-spacing-[321] {
+ --tw-spacing: 321 /* px */
+ }`,
+ );
+ });
+});
+
describe("offsets", () => {
test(`offsets ${OFFSETS[0]}-${OFFSETS[OFFSETS.length - 1]}`, async () => {
const expected: string[] = [];
diff --git a/tests/consts.test.ts b/tests/consts.test.ts
index 9b2e774..a622e29 100644
--- a/tests/consts.test.ts
+++ b/tests/consts.test.ts
@@ -4,6 +4,7 @@ import {
GRID_CELL_SIZE,
LINE_WIDTHS,
OFFSETS,
+ SPACING,
} from "../src/consts";
describe("default production values", () => {
@@ -25,4 +26,8 @@ describe("default production values", () => {
test("offsets", () => {
expect(OFFSETS).toEqual([...Array(128 + 16)].map((_, i) => i + 1));
});
+
+ test("spacing", () => {
+ expect(SPACING).toEqual([...Array(128)].map((_, i) => i + 1));
+ });
});
diff --git a/tests/hatching.test.ts b/tests/hatching.test.ts
new file mode 100644
index 0000000..264fecd
--- /dev/null
+++ b/tests/hatching.test.ts
@@ -0,0 +1,89 @@
+import { describe, test } from "vitest";
+import { DEFAULT_OPTS } from "../src/consts";
+import { css, expectCssToBe, generateTwCss } from "./utils";
+
+const DEFAULT_HATCHING_CSS = css`
+.${DEFAULT_OPTS.prefix}pattern-hatching {
+ --tw-line-size: 1;
+ --tw-spacing: 12;
+ --tw-offset-x: 0px;
+ --tw-offset-y: 0px;
+ --tw-hatching-angle: 45deg;
+ --tw-line-color: #ffffff;
+ --tw-unit: calc((var(--tw-line-size) + var(--tw-spacing))* 2);
+ --tw-line-stop: calc((var(--tw-line-size) / var(--tw-unit)* 100%) / 2);
+ --tw-1st-start-stop: calc(75% - var(--tw-line-stop));
+ --tw-1st-end-stop: calc(75% + var(--tw-line-stop));
+ --tw-2ed-start-stop: calc(25% - var(--tw-line-stop));
+ --tw-2ed-end-stop: calc(25% + var(--tw-line-stop));
+ background-image: linear-gradient(var(--tw-hatching-angle), transparent var(--tw-1st-start-stop), var(--tw-line-color) var(--tw-1st-start-stop), var(--tw-line-color) var(--tw-1st-end-stop), transparent var(--tw-1st-end-stop), transparent 100%), linear-gradient(var(--tw-hatching-angle), transparent var(--tw-2ed-start-stop), var(--tw-line-color) var(--tw-2ed-start-stop), var(--tw-line-color) var(--tw-2ed-end-stop), transparent var(--tw-2ed-end-stop), transparent 100%);
+ background-size: calc(var(--tw-unit)* 1px) calc(var(--tw-unit)* 1px);
+ background-position: calc(var(--tw-spacing)* -0.5px + var(--tw-offset-x)) var(--tw-offset-y)
+}`;
+
+describe("hatching patterns", () => {
+ test("hatching left-to-right pattern (default)", async () => {
+ expectCssToBe(
+ await generateTwCss(`${DEFAULT_OPTS.prefix}pattern-hatching`),
+ DEFAULT_HATCHING_CSS,
+ );
+ });
+
+ test("hatching right-to-left pattern", async () => {
+ expectCssToBe(
+ await generateTwCss(
+ `${DEFAULT_OPTS.prefix}pattern-hatching ${DEFAULT_OPTS.prefix}pattern-hatching-right-to-left`,
+ ),
+ css`${DEFAULT_HATCHING_CSS}
+ .${DEFAULT_OPTS.prefix}pattern-hatching-right-to-left {
+ --tw-hatching-angle: -45deg
+ }`,
+ );
+ });
+
+ test("cross-hatching pattern", async () => {
+ expectCssToBe(
+ await generateTwCss(`${DEFAULT_OPTS.prefix}pattern-cross-hatching`),
+ css`.${DEFAULT_OPTS.prefix}pattern-cross-hatching {
+ --tw-line-size: 1;
+ --tw-spacing: 12;
+ --tw-offset-x: 0px;
+ --tw-offset-y: 0px;
+ --tw-hatching-angle: 45deg;
+ --tw-line-color: #ffffff;
+ --tw-unit: calc((var(--tw-line-size) + var(--tw-spacing))* 2);
+ --tw-line-stop: calc((var(--tw-line-size) / var(--tw-unit)* 100%) / 2);
+ --tw-1st-start-stop: calc(75% - var(--tw-line-stop));
+ --tw-1st-end-stop: calc(75% + var(--tw-line-stop));
+ --tw-2ed-start-stop: calc(25% - var(--tw-line-stop));
+ --tw-2ed-end-stop: calc(25% + var(--tw-line-stop));
+ background-image: linear-gradient(var(--tw-hatching-angle), transparent var(--tw-1st-start-stop), var(--tw-line-color) var(--tw-1st-start-stop), var(--tw-line-color) var(--tw-1st-end-stop), transparent var(--tw-1st-end-stop), transparent 100%), linear-gradient(var(--tw-hatching-angle), transparent var(--tw-2ed-start-stop), var(--tw-line-color) var(--tw-2ed-start-stop), var(--tw-line-color) var(--tw-2ed-end-stop), transparent var(--tw-2ed-end-stop), transparent 100%), linear-gradient(calc(var(--tw-hatching-angle)* -1), transparent var(--tw-1st-start-stop), var(--tw-line-color) var(--tw-1st-start-stop), var(--tw-line-color) var(--tw-1st-end-stop), transparent var(--tw-1st-end-stop), transparent 100%), linear-gradient(calc(var(--tw-hatching-angle)* -1), transparent var(--tw-2ed-start-stop), var(--tw-line-color) var(--tw-2ed-start-stop), var(--tw-line-color) var(--tw-2ed-end-stop), transparent var(--tw-2ed-end-stop), transparent 100%);
+ background-size: calc(var(--tw-unit)* 1px) calc(var(--tw-unit)* 1px);
+ background-position: calc(var(--tw-spacing)* -0.5px + var(--tw-offset-x)) var(--tw-offset-y)
+ }`,
+ );
+ });
+});
+
+describe("hatching direction", () => {
+ test("left-to-right", async () => {
+ expectCssToBe(
+ await generateTwCss(
+ `${DEFAULT_OPTS.prefix}pattern-hatching-left-to-right`,
+ ),
+ css`.${DEFAULT_OPTS.prefix}pattern-hatching-left-to-right {
+ --tw-hatching-angle: 45deg
+ }`,
+ );
+ });
+ test("right-to-left", async () => {
+ expectCssToBe(
+ await generateTwCss(
+ `${DEFAULT_OPTS.prefix}pattern-hatching-right-to-left`,
+ ),
+ css`.${DEFAULT_OPTS.prefix}pattern-hatching-right-to-left {
+ --tw-hatching-angle: -45deg
+ }`,
+ );
+ });
+});