diff --git a/LICENSE b/LICENSE index e78443e..ca7c2db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 Klink +Copyright (c) 2026 Chris Klink Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ce57dd6..db74c31 100644 --- a/README.md +++ b/README.md @@ -589,6 +589,11 @@ If you'd rather not add a dependency, copy the source directly. Click to expand ```css +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + /* ── Squircle utilities ─────────────────────────────────────── */ /* squircle-amt-[n] sets the superellipse amount (default 2) */ /* squircle-* mirrors rounded-* variants: all, t, r, b, l, s, e, tl, tr, br, bl, ss, se, es, ee */ @@ -758,6 +763,11 @@ If you'd rather not add a dependency, copy the source directly. Click to expand ```css +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + /* ── squircle-radius() ──────────────────────────────────────── * Computes the visually-corrected border-radius for use with * corner-shape: superellipse(). A superellipse corner appears @@ -790,6 +800,11 @@ If you'd rather not add a dependency, copy the source directly. Click to expand squircleMergeConfig — tailwind-merge conflict config ```js +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + import { extendTailwindMerge } from "tailwind-merge"; const allRoundedGroups = [ @@ -837,6 +852,10 @@ export const twMerge = extendTailwindMerge(squircleMergeConfig); ````js import * as stylex from "@stylexjs/stylex"; +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ /** * StyleX squircle utilities — generated from this template by * `scripts/generate-stylex.ts`. diff --git a/package/scripts/generate-squircle-css.ts b/package/scripts/generate-squircle-css.ts index cb8d1f6..996e05a 100644 --- a/package/scripts/generate-squircle-css.ts +++ b/package/scripts/generate-squircle-css.ts @@ -31,6 +31,12 @@ function renderUtility(name: string, props: string[]): string { function generateCss(): string { const blocks: string[] = []; + blocks.push(`\ +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */`); + blocks.push(`\ /* ── Squircle utilities ─────────────────────────────────────── */ /* squircle-amt-[n] sets the superellipse amount (default ${DEFAULT_AMT}) */ diff --git a/package/src/panda.test.ts b/package/src/panda.test.ts index 9fed014..7a805fc 100644 --- a/package/src/panda.test.ts +++ b/package/src/panda.test.ts @@ -17,10 +17,7 @@ describe("panda preset shape", () => { it("registers a utility for every CAMEL_VARIANTS entry plus squircleAmount", () => { const keys = Object.keys(preset.utilities!.extend!).sort(); - const expected = [ - ...CAMEL_VARIANTS.map((v) => v.property), - "squircleAmount", - ].sort(); + const expected = [...CAMEL_VARIANTS.map((v) => v.property), "squircleAmount"].sort(); expect(keys).toEqual(expected); }); @@ -119,23 +116,35 @@ describe("panda preset transform output", () => { describe("panda preset options", () => { const mockToken = Object.assign(() => "1rem", { raw: () => undefined }) as any; - const mockArgs = (raw: string) => ({ token: mockToken, raw, utils: { colorMix: () => ({ invalid: true, value: "" }) } }); + const mockArgs = (raw: string) => ({ + token: mockToken, + raw, + utils: { colorMix: () => ({ invalid: true, value: "" }) }, + }); it("custom amtVar lands in every place the default --squircle-amt does", () => { const preset = squirclePandaPreset({ amtVar: "--my-amt" }); - const radiusOut = preset.utilities!.extend!["squircleRadius"]!.transform!("1rem", mockArgs("1rem")) as Record; - const radiusSupports = radiusOut[ - "@supports (corner-shape: superellipse(2))" - ] as Record; + const radiusOut = preset.utilities!.extend!["squircleRadius"]!.transform!( + "1rem", + mockArgs("1rem"), + ) as Record; + const radiusSupports = radiusOut["@supports (corner-shape: superellipse(2))"] as Record< + string, + string + >; expect(radiusSupports["--squircle-r"]).toContain("var(--my-amt, 2)"); expect(radiusSupports["cornerShape"]).toBe("superellipse(var(--my-amt, 2))"); - const amtOut = preset.utilities!.extend!["squircleAmount"]!.transform!("3", mockArgs("3")) as Record; + const amtOut = preset.utilities!.extend!["squircleAmount"]!.transform!( + "3", + mockArgs("3"), + ) as Record; expect(amtOut["--my-amt"]).toBe("3"); - const amtSupports = amtOut[ - "@supports (corner-shape: superellipse(2))" - ] as Record; + const amtSupports = amtOut["@supports (corner-shape: superellipse(2))"] as Record< + string, + string + >; expect(amtSupports["cornerShape"]).toBe("superellipse(var(--my-amt))"); const allOutput = JSON.stringify({ radiusOut, amtOut }); @@ -144,11 +153,11 @@ describe("panda preset options", () => { it("custom rVar threads through multi-prop side transforms", () => { const preset = squirclePandaPreset({ rVar: "--my-r" }); - const out = preset.utilities!.extend!["squircleTopRadius"]!.transform!("1rem", mockArgs("1rem")) as Record; - const supports = out["@supports (corner-shape: superellipse(2))"] as Record< - string, - string - >; + const out = preset.utilities!.extend!["squircleTopRadius"]!.transform!( + "1rem", + mockArgs("1rem"), + ) as Record; + const supports = out["@supports (corner-shape: superellipse(2))"] as Record; expect(supports["--my-r"]).toContain("calc(1rem"); expect(supports["borderTopLeftRadius"]).toBe("var(--my-r)"); expect(supports["borderTopRightRadius"]).toBe("var(--my-r)"); @@ -157,11 +166,11 @@ describe("panda preset options", () => { it("amtVar and rVar can both be overridden together", () => { const preset = squirclePandaPreset({ amtVar: "--a", rVar: "--r" }); - const out = preset.utilities!.extend!["squircleRadius"]!.transform!("1rem", mockArgs("1rem")) as Record; - const supports = out["@supports (corner-shape: superellipse(2))"] as Record< - string, - string - >; + const out = preset.utilities!.extend!["squircleRadius"]!.transform!( + "1rem", + mockArgs("1rem"), + ) as Record; + const supports = out["@supports (corner-shape: superellipse(2))"] as Record; expect(supports["--r"]).toContain("var(--a, 2)"); expect(supports["borderRadius"]).toBe("var(--r)"); expect(supports["cornerShape"]).toBe("superellipse(var(--a, 2))"); diff --git a/package/src/panda.ts b/package/src/panda.ts index 6e3e764..8c88928 100644 --- a/package/src/panda.ts +++ b/package/src/panda.ts @@ -1,3 +1,8 @@ +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + import { definePreset, type PropertyConfig } from "@pandacss/dev"; import { CAMEL_VARIANTS, @@ -31,9 +36,7 @@ export interface SquirclePandaPresetOptions { * `squircleTopLeft` mirror `roundedTopLeft`. The shape table is identical to * Panda's built-in radius utilities. */ -export function squirclePandaPreset( - options: SquirclePandaPresetOptions = {}, -) { +export function squirclePandaPreset(options: SquirclePandaPresetOptions = {}) { const amtVar = options.amtVar ?? DEFAULT_AMOUNT_VAR_NAME; const rVar = options.rVar ?? "--squircle-r"; @@ -48,8 +51,7 @@ export function squirclePandaPreset( utilities[variant.property] = { shorthand: variant.shorthand, values: "radii", - transform: (value) => - squircleCssObj(props, value, { amtVar, rVar, case: "camel" }) as any, + transform: (value) => squircleCssObj(props, value, { amtVar, rVar, case: "camel" }) as any, }; } diff --git a/package/src/squircle-css.test.ts b/package/src/squircle-css.test.ts index 84246f5..e165a51 100644 --- a/package/src/squircle-css.test.ts +++ b/package/src/squircle-css.test.ts @@ -105,5 +105,4 @@ describe("squircle.css utilities", () => { expect(css).not.toContain("squircle-amt-"); }); }); - }); diff --git a/package/src/squircle-radius.css b/package/src/squircle-radius.css index c551847..951bf22 100644 --- a/package/src/squircle-radius.css +++ b/package/src/squircle-radius.css @@ -1,3 +1,8 @@ +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + /* ── squircle-radius() ──────────────────────────────────────── * Computes the visually-corrected border-radius for use with * corner-shape: superellipse(). A superellipse corner appears diff --git a/package/src/stylex.template.ts b/package/src/stylex.template.ts index 4054324..50db85d 100644 --- a/package/src/stylex.template.ts +++ b/package/src/stylex.template.ts @@ -1,3 +1,8 @@ +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + import * as stylex from "@stylexjs/stylex"; /** diff --git a/package/src/stylex.ts b/package/src/stylex.ts index 7ff1cec..82a8bf5 100644 --- a/package/src/stylex.ts +++ b/package/src/stylex.ts @@ -5,6 +5,11 @@ // To modify variant shape, edit `renderVariant` in the generator. // To add/rename variants, edit `CAMEL_VARIANTS` and `VARIANTS` in src/variants.ts. +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + import * as stylex from "@stylexjs/stylex"; /** diff --git a/package/src/tailwind.ts b/package/src/tailwind.ts index f638b99..cd430d7 100644 --- a/package/src/tailwind.ts +++ b/package/src/tailwind.ts @@ -1,3 +1,8 @@ +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + import plugin from "tailwindcss/plugin"; import { DEFAULT_AMOUNT_VAR_NAME, @@ -26,37 +31,37 @@ const squircle: ReturnType> = plugin.withOptions((options = {}) => // eslint-disable-next-line @typescript-eslint/unbound-method ({ matchUtilities, theme }) => { - const amtVar = options.amtVar ?? options["amt-var"] ?? DEFAULT_AMOUNT_VAR_NAME; - const rVar = options.rVar ?? options["r-var"] ?? DEFAULT_R_VAR_NAME; - const prefix = options.prefix ?? "squircle"; - const radiusValues = theme("borderRadius"); + const amtVar = options.amtVar ?? options["amt-var"] ?? DEFAULT_AMOUNT_VAR_NAME; + const rVar = options.rVar ?? options["r-var"] ?? DEFAULT_R_VAR_NAME; + const prefix = options.prefix ?? "squircle"; + const radiusValues = theme("borderRadius"); + + matchUtilities( + { + [`${prefix}-amt`]: (value: string) => ({ + [amtVar]: value, + [SUPPORTS_RULE]: { + "corner-shape": `superellipse(var(${amtVar}))`, + }, + }), + }, + { type: "number" }, + ); + for (const [suffix, props] of variantEntries()) { + const name = suffix ? `${prefix}-${suffix}` : prefix; matchUtilities( { - [`${prefix}-amt`]: (value: string) => ({ - [amtVar]: value, - [SUPPORTS_RULE]: { - "corner-shape": `superellipse(var(${amtVar}))`, - }, - }), + [name]: (value: string) => + squircleCssObj(props, value, { amtVar, rVar }) as Record< + string, + string | Record + >, }, - { type: "number" }, + { type: "length", values: radiusValues }, ); - - for (const [suffix, props] of variantEntries()) { - const name = suffix ? `${prefix}-${suffix}` : prefix; - matchUtilities( - { - [name]: (value: string) => - squircleCssObj(props, value, { amtVar, rVar }) as Record< - string, - string | Record - >, - }, - { type: "length", values: radiusValues }, - ); - } - }); + } + }); export default squircle; diff --git a/package/src/test-utils.ts b/package/src/test-utils.ts index 72ad974..6d081cd 100644 --- a/package/src/test-utils.ts +++ b/package/src/test-utils.ts @@ -46,7 +46,8 @@ async function loadModule(id: string, base: string, _resourceHint: "plugin" | "c } function extractUtilitiesLayer(css: string): string { - const match = /@layer utilities \{([\s\S]*)\}\s*$/.exec(css); + // Tailwind preserves /*! ... */ license comments after the final layer. + const match = /@layer utilities \{([\s\S]*)\}\s*(?:\/\*![\s\S]*?\*\/\s*)*$/.exec(css); return match?.[1]?.trim() ?? ""; } diff --git a/package/src/variants.ts b/package/src/variants.ts index 9f2ea0a..ad05bb2 100644 --- a/package/src/variants.ts +++ b/package/src/variants.ts @@ -1,3 +1,8 @@ +/*! + * @klinking/squircle — MIT License — Copyright (c) 2026 Chris Klink + * https://squircle.klink.ing/ · https://github.com/klink-ing/squircle + */ + export const DEFAULT_AMT = 2 as const; export const DEFAULT_AMOUNT_VAR_NAME = "--squircle-amt" as const; export const DEFAULT_R_VAR_NAME = "--squircle-r" as const; @@ -153,7 +158,10 @@ function toCamel(kebab: string): string { return camel; } -export type CssLikeObject = Record>; +export type CssLikeObject = Record< + string, + string | number | null | Record +>; export interface SquircleCssObjOptions { /** Override the `--squircle-amt` variable name (default `--squircle-amt`). */ diff --git a/package/vite.config.ts b/package/vite.config.ts index 66aa937..c7a847f 100644 --- a/package/vite.config.ts +++ b/package/vite.config.ts @@ -36,13 +36,7 @@ export default defineConfig({ }, test: { command: "echo 'All tests passed'", - dependsOn: [ - "test:tailwind", - "test:css", - "test:radius", - "test:panda", - "test:stylex", - ], + dependsOn: ["test:tailwind", "test:css", "test:radius", "test:panda", "test:stylex"], }, "generate:stylex": { command: "tsx scripts/generate-stylex.ts", diff --git a/vite.config.ts b/vite.config.ts index 9342707..4e37da5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,8 +5,7 @@ export default defineConfig({ cache: true, tasks: { fmt: { - command: - 'bash -c \'OUT=$(vp fmt --check 2>&1); echo "$OUT"; ! echo "$OUT" | grep -q "Unable to load plugin"\'', + command: "vp fmt --check", dependsOn: ["@klinking/squircle#build"], }, lint: { diff --git a/website/astro.config.mjs b/website/astro.config.mjs index c7ff44d..1d26fb4 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -28,10 +28,7 @@ function stylexForExternalModules() { enforce: "pre", async transform(code, id) { if (!/\.m?jsx?$|\.tsx?$/.test(id)) return null; - if ( - !id.includes("/package/dist/stylex/index.mjs") && - !id.includes(".stylex.") - ) { + if (!id.includes("/package/dist/stylex/index.mjs") && !id.includes(".stylex.")) { return null; } const result = await babel.transformAsync(code, { diff --git a/website/examples/stylex/src/App.tsx b/website/examples/stylex/src/App.tsx index fff9a17..aaec6d6 100644 --- a/website/examples/stylex/src/App.tsx +++ b/website/examples/stylex/src/App.tsx @@ -38,9 +38,7 @@ export default function App() { return ( squircle StyleX Demo - - Squircle utilities authored as StyleX dynamic styles. - + Squircle utilities authored as StyleX dynamic styles. @@ -87,9 +85,7 @@ export default function App() { Squircle amount - - Higher = more square. Default is 2. - + Higher = more square. Default is 2.
squircleMergeConfig
- Squircle utilities authored as StyleX dynamic styles. -
Squircle utilities authored as StyleX dynamic styles.
- Higher = more square. Default is 2. -
Higher = more square. Default is 2.