Skip to content

Commit 5fc5852

Browse files
authored
Merge pull request #3 from yet3/feat/hatching
Add hatching patterns
2 parents cc82079 + 8ef41b3 commit 5fc5852

File tree

9 files changed

+317
-17
lines changed

9 files changed

+317
-17
lines changed

example/src/common/Pattern.svelte

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
<script lang="ts">
22
import HtmlCode from './HtmlCode.svelte';
33
4+
export interface IPatternConfig {
5+
name: string;
6+
description: string;
7+
}
48
export interface IPattern {
59
name: string;
610
class: string;
7-
configClasses: { name: string; description: string }[];
11+
configClasses: IPatternConfig[];
812
}
913
1014
interface IProps {
@@ -20,10 +24,10 @@
2024
<h2 class="mb-4 text-3xl font-medium">{name}</h2>
2125

2226
<h3 class="font-medium text-white/90 ~2xs/xs:~text-base/lg">Configuration classes:</h3>
23-
<ul class="ml-2 mb-4 ~2xs/xs:~text-sm/base grid content-start">
27+
<ul class="ml-2 mb-4 ~2xs/xs:~text-sm/base grid content-start gap-1">
2428
{#each configClasses as config}
2529
<li class="flex max-xl:flex-col">
26-
<span>-<code class="ml-1 font-medium text-class">{config.name}</code></span>
30+
<code class="ml-1 font-medium text-class">.{config.name}</code>
2731
<p class="ml-3 text-white/75">{config.description}</p>
2832
</li>
2933
{/each}

example/src/routes/+page.svelte

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,63 @@
11
<script lang="ts">
2-
import Pattern, { type IPattern } from '$common/Pattern.svelte';
2+
import Pattern, { type IPattern, type IPatternConfig } from '$common/Pattern.svelte';
33
import Header from '$modules/Header.svelte';
44
import githubCodeStyle from 'svelte-highlight/styles/github-dark';
55
6+
const LINE_CONFIGS: IPatternConfig[] = [
7+
{ name: 'bg-pattern-line-2', description: 'Configure width of pattern lines' },
8+
{
9+
name: 'bg-pattern-line-[48]',
10+
description: 'Configure width of pattern lines (custom value, without unit)'
11+
},
12+
{ name: 'bg-pattern-line-red-500', description: 'Configure color of pattern lines' },
13+
{
14+
name: 'bg-pattern-line-[#a8a8a8]',
15+
description: 'Configure color of pattern lines (custom value)'
16+
}
17+
];
18+
19+
const SPACING_CONFIGS: IPatternConfig[] = [
20+
{ name: 'bg-pattern-spacing-48', description: 'Configure the spacing between lines' },
21+
{
22+
name: 'bg-pattern-spacing-[225]',
23+
description: 'Configure the spacing between lines (custom value, without unit)'
24+
}
25+
];
26+
27+
const HATCHING_DIRECTION_CONFIGS: IPatternConfig[] = [
28+
{
29+
name: 'bg-pattern-hatching-left-to-right',
30+
description: 'Configure hatching to start from top left'
31+
},
32+
{
33+
name: 'bg-pattern-hatching-right-to-left',
34+
description: 'Configure hatching to start from top right'
35+
}
36+
];
37+
638
const PATTERNS: IPattern[] = [
739
{
840
name: 'Grid',
941
configClasses: [
10-
{ name: 'bg-pattern-line-2', description: 'Configure width of pattern lines' },
11-
{
12-
name: 'bg-pattern-line-[48]',
13-
description: 'Configure width of pattern lines (custom value, must not contain unit)'
14-
},
15-
{ name: 'bg-pattern-line-red-500', description: 'Configure color of pattern lines' },
16-
{
17-
name: 'bg-pattern-line-[#a8a8a8]',
18-
description: 'Configure color of pattern lines (custom value)'
19-
},
42+
...LINE_CONFIGS,
2043
{ name: 'bg-pattern-cell-48', description: 'Configure size of grid pattern cells' },
2144
{
2245
name: 'bg-pattern-cell-[225]',
2346
description: 'Configure size of grid pattern cells (custom value)'
2447
}
2548
],
2649
class: 'bg-blue-500 bg-pattern-grid bg-pattern-line-0.5 bg-pattern-cell-32'
50+
},
51+
{
52+
name: 'Hatching',
53+
configClasses: [...LINE_CONFIGS, ...SPACING_CONFIGS, ...HATCHING_DIRECTION_CONFIGS],
54+
class:
55+
'bg-blue-500 bg-pattern-hatching bg-pattern-line-0.5 bg-pattern-spacing-16 bg-pattern-hatching-left-to-right'
56+
},
57+
{
58+
name: 'Cross-Hatching',
59+
configClasses: [...LINE_CONFIGS, ...SPACING_CONFIGS, ...HATCHING_DIRECTION_CONFIGS],
60+
class: 'bg-blue-500 bg-pattern-cross-hatching bg-pattern-line-0.5 bg-pattern-spacing-16'
2761
}
2862
];
2963
</script>

src/consts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const LINE_WIDTHS = [
44
0.5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16,
55
];
66
export const GRID_CELL_SIZE: number[] = [...Array(128)].map((_, i) => i + 1);
7+
export const SPACING: number[] = [...Array(128)].map((_, i) => i + 1);
78
export const OFFSETS: number[] = [...Array(128 + 16)].map((_, i) => i + 1);
89

910
export const DEFAULT_OPTS: IResolvedOpts = {

src/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import plugin from "tailwindcss/plugin";
2-
import { GRID_CELL_SIZE, LINE_WIDTHS, OFFSETS } from "./consts";
2+
import { GRID_CELL_SIZE, LINE_WIDTHS, OFFSETS, SPACING } from "./consts";
3+
import { arrToTwConfig } from "./lib/arrToTwConfig";
34
import { generateCellSizes, matchCellSize } from "./lib/cellSizes";
45
import {
56
generateLineColors,
@@ -8,25 +9,36 @@ import {
89
} from "./lib/line";
910
import { generateOffsets, matchOffsets } from "./lib/offsets";
1011
import { resolveOptions } from "./lib/resolveOptions";
12+
import { generateSpacing, matchSpacing } from "./lib/spacing";
1113
import { generateGridClass } from "./patterns/grid";
14+
import { generateHatchingClass, genreateHatchingDirection } from "./patterns/hatching";
1215
import type { IOptions } from "./types";
13-
import { arrToTwConfig } from "./lib/arrToTwConfig";
1416

1517
export default plugin.withOptions<IOptions | undefined>(
1618
(options) => (api) => {
1719
const { addUtilities, matchUtilities, e } = api;
1820
const opts = resolveOptions(options);
1921

2022
addUtilities([
23+
// Patterns
2124
generateGridClass(e(`${opts.prefix}pattern-grid`)),
25+
generateHatchingClass(e(`${opts.prefix}pattern-hatching`)),
26+
generateHatchingClass(e(`${opts.prefix}pattern-cross-hatching`), {
27+
isCrossHatch: true,
28+
}),
29+
30+
// Configs
2231
generateLineWidths(api, opts),
2332
generateCellSizes(api, opts),
33+
generateSpacing(api, opts),
34+
genreateHatchingDirection(api, opts),
2435
generateLineColors(api, opts),
2536
generateOffsets(api, opts),
2637
]);
2738

2839
matchUtilities({
2940
...matchCellSize(api, opts),
41+
...matchSpacing(api, opts),
3042
...matchLineWidthsAndColors(api, opts),
3143
...matchOffsets(api, opts),
3244
});
@@ -35,6 +47,7 @@ export default plugin.withOptions<IOptions | undefined>(
3547
theme: {
3648
bgPatternLineWidth: arrToTwConfig(LINE_WIDTHS),
3749
bgPatternCellSize: arrToTwConfig(GRID_CELL_SIZE),
50+
bgPatternSpacing: arrToTwConfig(SPACING),
3851
bgPatternOffsets: arrToTwConfig(OFFSETS, "px"),
3952
},
4053
}),

src/lib/spacing.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { CSSRuleObject, PluginAPI } from "tailwindcss/types/config";
2+
import type { IOptions } from "../types";
3+
4+
export const generateSpacing = (
5+
api: PluginAPI,
6+
opts: IOptions,
7+
): CSSRuleObject => {
8+
const spacing = api.theme("bgPatternSpacing");
9+
const styles: Record<string, unknown> = {};
10+
if (spacing) {
11+
for (const key in spacing) {
12+
const value = spacing[key];
13+
styles[`.${api.e(`${opts.prefix}pattern-spacing-${key}`)}`] = {
14+
"--tw-spacing": `${value} /* px */`,
15+
} as unknown as CSSRuleObject;
16+
}
17+
}
18+
return styles as CSSRuleObject;
19+
};
20+
21+
export const matchSpacing = (api: PluginAPI, opts: IOptions) => ({
22+
[api.e(`${opts.prefix}pattern-spacing`)]: (value: string) => {
23+
return {
24+
"--tw-spacing": `${value} /* px */`,
25+
} as unknown as CSSRuleObject;
26+
}
27+
});

src/patterns/hatching.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type { CSSRuleObject, PluginAPI } from "tailwindcss/types/config";
2+
import type { IOptions } from "../types";
3+
4+
interface IOpts {
5+
lineSize: number;
6+
spacing: number;
7+
offsetX: number;
8+
offsetY: number;
9+
isCrossHatch: boolean;
10+
isRightLeaning: boolean;
11+
lineColor: string;
12+
}
13+
14+
export const generateHatchingClass = (
15+
className: string,
16+
_opts?: Partial<IOpts>,
17+
): CSSRuleObject => {
18+
const opts: IOpts = {
19+
lineSize: 1,
20+
lineColor: "#ffffff",
21+
spacing: 12,
22+
offsetX: 0,
23+
offsetY: 0,
24+
isCrossHatch: false,
25+
isRightLeaning: false,
26+
..._opts,
27+
};
28+
29+
let bgImageCode = `linear-gradient(
30+
var(--tw-hatching-angle),
31+
transparent var(--tw-1st-start-stop),
32+
var(--tw-line-color) var(--tw-1st-start-stop),
33+
var(--tw-line-color) var(--tw-1st-end-stop),
34+
transparent var(--tw-1st-end-stop),
35+
transparent 100%
36+
),
37+
linear-gradient(
38+
var(--tw-hatching-angle),
39+
transparent var(--tw-2ed-start-stop),
40+
var(--tw-line-color) var(--tw-2ed-start-stop),
41+
var(--tw-line-color) var(--tw-2ed-end-stop),
42+
transparent var(--tw-2ed-end-stop),
43+
transparent 100%
44+
)
45+
`;
46+
47+
if (opts.isCrossHatch) {
48+
bgImageCode += `, linear-gradient(
49+
calc(var(--tw-hatching-angle) * -1),
50+
transparent var(--tw-1st-start-stop),
51+
var(--tw-line-color) var(--tw-1st-start-stop),
52+
var(--tw-line-color) var(--tw-1st-end-stop),
53+
transparent var(--tw-1st-end-stop),
54+
transparent 100%
55+
),
56+
linear-gradient(
57+
calc(var(--tw-hatching-angle) * -1),
58+
transparent var(--tw-2ed-start-stop),
59+
var(--tw-line-color) var(--tw-2ed-start-stop),
60+
var(--tw-line-color) var(--tw-2ed-end-stop),
61+
transparent var(--tw-2ed-end-stop),
62+
transparent 100%
63+
)`;
64+
}
65+
66+
return {
67+
[`.${className}`]: {
68+
"--tw-line-size": opts.lineSize.toString(),
69+
"--tw-spacing": opts.spacing.toString(),
70+
"--tw-offset-x": `${opts.offsetX * -1}px`,
71+
"--tw-offset-y": `${opts.offsetY * -1}px`,
72+
"--tw-hatching-angle": `${opts.isRightLeaning ? -45 : 45}deg`,
73+
"--tw-line-color": opts.lineColor,
74+
75+
"--tw-unit": "calc((var(--tw-line-size) + var(--tw-spacing)) * 2)",
76+
"--tw-line-stop":
77+
"calc((var(--tw-line-size) / var(--tw-unit) * 100%) / 2)",
78+
"--tw-1st-start-stop": "calc(75% - var(--tw-line-stop))",
79+
"--tw-1st-end-stop": "calc(75% + var(--tw-line-stop))",
80+
"--tw-2ed-start-stop": "calc(25% - var(--tw-line-stop))",
81+
"--tw-2ed-end-stop": "calc(25% + var(--tw-line-stop))",
82+
backgroundImage: bgImageCode,
83+
backgroundSize: "calc(var(--tw-unit) * 1px) calc(var(--tw-unit) * 1px)",
84+
backgroundPosition:
85+
"calc(var(--tw-spacing) * -0.5px + var(--tw-offset-x)) var(--tw-offset-y)",
86+
},
87+
};
88+
};
89+
90+
export const genreateHatchingDirection = (
91+
api: PluginAPI,
92+
opts: IOptions,
93+
): CSSRuleObject => {
94+
return {
95+
[`.${api.e(`${opts.prefix}pattern-hatching-left-to-right`)}`]: {
96+
"--tw-hatching-angle": "45deg",
97+
},
98+
[`.${api.e(`${opts.prefix}pattern-hatching-right-to-left`)}`]: {
99+
"--tw-hatching-angle": "-45deg",
100+
},
101+
} as unknown as CSSRuleObject;
102+
};

tests/index.test.ts renamed to tests/config.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, test } from "vitest";
2-
import { DEFAULT_OPTS, LINE_WIDTHS, OFFSETS } from "../src/consts";
2+
import { DEFAULT_OPTS, LINE_WIDTHS, OFFSETS, SPACING } from "../src/consts";
33
import { css, expectCssToBe, generateTwCss } from "./utils";
44

55
describe("line widths", () => {
@@ -24,6 +24,31 @@ describe("line widths", () => {
2424
});
2525
});
2626

27+
describe("spacing", () => {
28+
test(`spacing ${SPACING[0]}-${SPACING[SPACING.length - 1]}`, async () => {
29+
const expected: string[] = [];
30+
const classes: string[] = [];
31+
for (const value of SPACING) {
32+
classes.push(`${DEFAULT_OPTS.prefix}pattern-spacing-${value}`);
33+
expected.push(
34+
css`.${DEFAULT_OPTS.prefix}pattern-spacing-${value} {
35+
--tw-spacing: ${value} /* px */
36+
}`,
37+
);
38+
}
39+
expectCssToBe(await generateTwCss(classes.join(" ")), expected.join(""));
40+
});
41+
42+
test("custom spacing", async () => {
43+
expectCssToBe(
44+
await generateTwCss(`${DEFAULT_OPTS.prefix}pattern-spacing-[321]`),
45+
css`.${DEFAULT_OPTS.prefix}pattern-spacing-[321] {
46+
--tw-spacing: 321 /* px */
47+
}`,
48+
);
49+
});
50+
});
51+
2752
describe("offsets", () => {
2853
test(`offsets ${OFFSETS[0]}-${OFFSETS[OFFSETS.length - 1]}`, async () => {
2954
const expected: string[] = [];

tests/consts.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
GRID_CELL_SIZE,
55
LINE_WIDTHS,
66
OFFSETS,
7+
SPACING,
78
} from "../src/consts";
89

910
describe("default production values", () => {
@@ -25,4 +26,8 @@ describe("default production values", () => {
2526
test("offsets", () => {
2627
expect(OFFSETS).toEqual([...Array(128 + 16)].map((_, i) => i + 1));
2728
});
29+
30+
test("spacing", () => {
31+
expect(SPACING).toEqual([...Array(128)].map((_, i) => i + 1));
32+
});
2833
});

0 commit comments

Comments
 (0)