Skip to content

Commit 30169b2

Browse files
dogmarclaude
andcommitted
chore: infer node version from .node-version + use real panda types
- Replace hardcoded `node-version: "22"` with `node-version-file: ".node-version"` in all four GitHub Actions workflows - Replace hand-rolled Panda type stubs with `definePreset` and `PropertyConfig` from `@pandacss/dev` - Add `@pandacss/dev` as a devDependency for build-time type checking Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 43323f2 commit 30169b2

8 files changed

Lines changed: 40 additions & 72 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818

1919
- uses: voidzero-dev/setup-vp@v1
2020
with:
21-
node-version: "22"
21+
node-version-file: ".node-version"
2222
cache: true
2323

2424
- run: vp install

‎.github/workflows/deploy-site.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222

2323
- uses: voidzero-dev/setup-vp@v1
2424
with:
25-
node-version: "22"
25+
node-version-file: ".node-version"
2626
cache: true
2727

2828
- run: vp install

‎.github/workflows/sync-readme.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626

2727
- uses: voidzero-dev/setup-vp@v1
2828
with:
29-
node-version: "22"
29+
node-version-file: ".node-version"
3030
cache: true
3131

3232
- run: vp install

‎.github/workflows/sync-stylex.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424

2525
- uses: voidzero-dev/setup-vp@v1
2626
with:
27-
node-version: "22"
27+
node-version-file: ".node-version"
2828
cache: true
2929

3030
- run: vp install

‎package/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"devDependencies": {
4343
"@babel/core": "^7.29.0",
4444
"@babel/preset-typescript": "^7.28.0",
45+
"@pandacss/dev": "^1.11.0",
4546
"@stylexjs/babel-plugin": "^0.18.3",
4647
"@stylexjs/stylex": "^0.18.3",
4748
"@tailwindcss/vite": "^4.2.2",

‎package/src/panda.test.ts‎

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ describe("panda preset shape", () => {
1010
});
1111

1212
it("registers the squircleSupported condition", () => {
13-
expect(preset.conditions.extend["squircleSupported"]).toBe(
13+
expect(preset.conditions!.extend!["squircleSupported"]).toBe(
1414
"@supports (corner-shape: superellipse(2))",
1515
);
1616
});
1717

1818
it("registers a utility for every CAMEL_VARIANTS entry plus squircleAmount", () => {
19-
const keys = Object.keys(preset.utilities.extend).sort();
19+
const keys = Object.keys(preset.utilities!.extend!).sort();
2020
const expected = [
2121
...CAMEL_VARIANTS.map((v) => v.property),
2222
"squircleAmount",
@@ -26,15 +26,15 @@ describe("panda preset shape", () => {
2626

2727
it("uses Panda's built-in `radii` token category for radius utilities", () => {
2828
for (const variant of CAMEL_VARIANTS) {
29-
const u = preset.utilities.extend[variant.property];
29+
const u = preset.utilities!.extend![variant.property];
3030
if (!u) throw new Error(`missing utility for ${variant.property}`);
3131
expect(u.values).toBe("radii");
3232
expect(u.shorthand).toBe(variant.shorthand);
3333
}
3434
});
3535

3636
it("squircleAmount accepts numeric values via shorthand squircleAmt", () => {
37-
const u = preset.utilities.extend["squircleAmount"]!;
37+
const u = preset.utilities!.extend!["squircleAmount"]!;
3838
expect(u.shorthand).toBe("squircleAmt");
3939
expect(u.values).toEqual({ type: "number" });
4040
});
@@ -43,11 +43,13 @@ describe("panda preset shape", () => {
4343
describe("panda preset transform output", () => {
4444
const preset = squirclePandaPreset();
4545
const radiusToken = "var(--radii-md)"; // Panda passes the resolved CSS variable string
46+
const mockToken = Object.assign(() => radiusToken, { raw: () => undefined }) as any;
4647

4748
it("squircleRadius (all corners) emits camelCase keys with @supports block", () => {
48-
const out = preset.utilities.extend["squircleRadius"]!.transform!(radiusToken, {
49-
token: () => radiusToken,
49+
const out = preset.utilities!.extend!["squircleRadius"]!.transform!(radiusToken, {
50+
token: mockToken,
5051
raw: "md",
52+
utils: { colorMix: () => ({ invalid: true, value: "" }) },
5153
});
5254
expect(out).toMatchInlineSnapshot(`
5355
{
@@ -62,9 +64,10 @@ describe("panda preset transform output", () => {
6264
});
6365

6466
it("squircleTopLeftRadius (single corner) inlines calc without --squircle-r", () => {
65-
const out = preset.utilities.extend["squircleTopLeftRadius"]!.transform!(radiusToken, {
66-
token: () => radiusToken,
67+
const out = preset.utilities!.extend!["squircleTopLeftRadius"]!.transform!(radiusToken, {
68+
token: mockToken,
6769
raw: "md",
70+
utils: { colorMix: () => ({ invalid: true, value: "" }) },
6871
});
6972
expect(out).toMatchInlineSnapshot(`
7073
{
@@ -78,9 +81,10 @@ describe("panda preset transform output", () => {
7881
});
7982

8083
it("squircleTopRadius (multi-prop side) shares --squircle-r across both corners", () => {
81-
const out = preset.utilities.extend["squircleTopRadius"]!.transform!(radiusToken, {
82-
token: () => radiusToken,
84+
const out = preset.utilities!.extend!["squircleTopRadius"]!.transform!(radiusToken, {
85+
token: mockToken,
8386
raw: "md",
87+
utils: { colorMix: () => ({ invalid: true, value: "" }) },
8488
});
8589
expect(out).toMatchInlineSnapshot(`
8690
{
@@ -97,9 +101,10 @@ describe("panda preset transform output", () => {
97101
});
98102

99103
it("squircleAmount sets the variable and gates corner-shape", () => {
100-
const out = preset.utilities.extend["squircleAmount"]!.transform!("3", {
101-
token: () => "3",
104+
const out = preset.utilities!.extend!["squircleAmount"]!.transform!("3", {
105+
token: mockToken,
102106
raw: "3",
107+
utils: { colorMix: () => ({ invalid: true, value: "" }) },
103108
});
104109
expect(out).toMatchInlineSnapshot(`
105110
{
@@ -113,61 +118,46 @@ describe("panda preset transform output", () => {
113118
});
114119

115120
describe("panda preset options", () => {
121+
const mockToken = Object.assign(() => "1rem", { raw: () => undefined }) as any;
122+
const mockArgs = (raw: string) => ({ token: mockToken, raw, utils: { colorMix: () => ({ invalid: true, value: "" }) } });
123+
116124
it("custom amtVar lands in every place the default --squircle-amt does", () => {
117125
const preset = squirclePandaPreset({ amtVar: "--my-amt" });
118126

119-
// 1. radius transform — @supports calc references the custom var, and
120-
// cornerShape uses it as the superellipse argument.
121-
const radiusOut = preset.utilities.extend["squircleRadius"]!.transform!("1rem", {
122-
token: () => "1rem",
123-
raw: "1rem",
124-
}) as Record<string, unknown>;
127+
const radiusOut = preset.utilities!.extend!["squircleRadius"]!.transform!("1rem", mockArgs("1rem")) as Record<string, unknown>;
125128
const radiusSupports = radiusOut[
126129
"@supports (corner-shape: superellipse(2))"
127130
] as Record<string, string>;
128131
expect(radiusSupports["--squircle-r"]).toContain("var(--my-amt, 2)");
129132
expect(radiusSupports["cornerShape"]).toBe("superellipse(var(--my-amt, 2))");
130133

131-
// 2. squircleAmount transform — writes the custom var and the @supports
132-
// block reads it back.
133-
const amtOut = preset.utilities.extend["squircleAmount"]!.transform!("3", {
134-
token: () => "3",
135-
raw: "3",
136-
}) as Record<string, unknown>;
134+
const amtOut = preset.utilities!.extend!["squircleAmount"]!.transform!("3", mockArgs("3")) as Record<string, unknown>;
137135
expect(amtOut["--my-amt"]).toBe("3");
138136
const amtSupports = amtOut[
139137
"@supports (corner-shape: superellipse(2))"
140138
] as Record<string, string>;
141139
expect(amtSupports["cornerShape"]).toBe("superellipse(var(--my-amt))");
142140

143-
// 3. The default name does not appear anywhere when overridden.
144141
const allOutput = JSON.stringify({ radiusOut, amtOut });
145142
expect(allOutput).not.toContain("--squircle-amt");
146143
});
147144

148145
it("custom rVar threads through multi-prop side transforms", () => {
149146
const preset = squirclePandaPreset({ rVar: "--my-r" });
150-
const out = preset.utilities.extend["squircleTopRadius"]!.transform!("1rem", {
151-
token: () => "1rem",
152-
raw: "1rem",
153-
}) as Record<string, unknown>;
147+
const out = preset.utilities!.extend!["squircleTopRadius"]!.transform!("1rem", mockArgs("1rem")) as Record<string, unknown>;
154148
const supports = out["@supports (corner-shape: superellipse(2))"] as Record<
155149
string,
156150
string
157151
>;
158152
expect(supports["--my-r"]).toContain("calc(1rem");
159153
expect(supports["borderTopLeftRadius"]).toBe("var(--my-r)");
160154
expect(supports["borderTopRightRadius"]).toBe("var(--my-r)");
161-
// And the default --squircle-r is gone.
162155
expect(JSON.stringify(out)).not.toContain("--squircle-r");
163156
});
164157

165158
it("amtVar and rVar can both be overridden together", () => {
166159
const preset = squirclePandaPreset({ amtVar: "--a", rVar: "--r" });
167-
const out = preset.utilities.extend["squircleRadius"]!.transform!("1rem", {
168-
token: () => "1rem",
169-
raw: "1rem",
170-
}) as Record<string, unknown>;
160+
const out = preset.utilities!.extend!["squircleRadius"]!.transform!("1rem", mockArgs("1rem")) as Record<string, unknown>;
171161
const supports = out["@supports (corner-shape: superellipse(2))"] as Record<
172162
string,
173163
string

‎package/src/panda.ts‎

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { definePreset, type PropertyConfig } from "@pandacss/dev";
12
import {
23
CAMEL_VARIANTS,
34
DEFAULT_AMOUNT_VAR_NAME,
@@ -6,34 +7,13 @@ import {
67
variantEntries,
78
} from "./variants";
89

9-
/**
10-
* Panda CSS utility entry shape (subset). We do not depend on `@pandacss/dev`
11-
* at runtime — the preset is a plain object literal and the consumer's Panda
12-
* install loads it. Typing the public surface manually keeps `@pandacss/dev`
13-
* an *optional* peer dependency and avoids versioning entanglement.
14-
*/
15-
type PandaUtility = {
16-
shorthand?: string | string[];
17-
values?: string | string[] | Record<string, string> | { type: string };
18-
transform?: (
19-
value: string,
20-
helpers: { token: (path: string) => string; raw: string },
21-
) => Record<string, unknown>;
22-
};
23-
2410
export interface SquirclePandaPresetOptions {
2511
/** CSS custom property name for the superellipse amount (default: "--squircle-amt"). */
2612
amtVar?: string;
2713
/** CSS custom property name for the intermediate corrected radius (default: "--squircle-r"). */
2814
rVar?: string;
2915
}
3016

31-
export interface SquirclePandaPreset {
32-
name: string;
33-
utilities: { extend: Record<string, PandaUtility> };
34-
conditions: { extend: Record<string, string> };
35-
}
36-
3717
/**
3818
* Build the Panda preset object. Pass directly to `presets:` in `panda.config.ts`:
3919
*
@@ -53,11 +33,11 @@ export interface SquirclePandaPreset {
5333
*/
5434
export function squirclePandaPreset(
5535
options: SquirclePandaPresetOptions = {},
56-
): SquirclePandaPreset {
36+
) {
5737
const amtVar = options.amtVar ?? DEFAULT_AMOUNT_VAR_NAME;
5838
const rVar = options.rVar ?? "--squircle-r";
5939

60-
const utilities: Record<string, PandaUtility> = {};
40+
const utilities: Record<string, PropertyConfig> = {};
6141

6242
const variantBySuffix = new Map(variantEntries());
6343

@@ -68,34 +48,31 @@ export function squirclePandaPreset(
6848
utilities[variant.property] = {
6949
shorthand: variant.shorthand,
7050
values: "radii",
71-
transform: (value: string) =>
72-
squircleCssObj(props, value, { amtVar, rVar, case: "camel" }) as Record<
73-
string,
74-
unknown
75-
>,
51+
transform: (value) =>
52+
squircleCssObj(props, value, { amtVar, rVar, case: "camel" }) as any,
7653
};
7754
}
7855

7956
utilities["squircleAmount"] = {
8057
shorthand: "squircleAmt",
8158
values: { type: "number" },
82-
transform: (value: string) => ({
59+
transform: (value) => ({
8360
[amtVar]: value,
8461
[SUPPORTS_RULE]: {
8562
cornerShape: `superellipse(var(${amtVar}))`,
8663
},
8764
}),
8865
};
8966

90-
return {
67+
return definePreset({
9168
name: "@klinking/squircle",
9269
utilities: { extend: utilities },
9370
conditions: {
9471
extend: {
9572
squircleSupported: SUPPORTS_RULE,
9673
},
9774
},
98-
};
75+
});
9976
}
10077

10178
export default squirclePandaPreset;

‎pnpm-lock.yaml‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)