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: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ We're dedicated to pushing modern WordPress development forward through our open

- 🔄 Transforms `@wordpress/*` imports into global `wp.*` references
- 📦 Generates dependency manifest for WordPress enqueuing
- 🎨 Generates theme.json from Tailwind CSS configuration
- 🎨 Generates theme.json from Tailwind CSS configuration (colors, fonts, font sizes, border radius)
- 🔥 Hot Module Replacement (HMR) support for the WordPress editor

## Installation
Expand Down Expand Up @@ -153,10 +153,19 @@ export default defineConfig({
lg: 'Large',
},

// Optional: Configure border radius labels
borderRadiusLabels: {
sm: 'Small',
md: 'Medium',
lg: 'Large',
full: 'Full',
},

// Optional: Disable specific transformations
disableTailwindColors: false,
disableTailwindFonts: false,
disableTailwindFontSizes: false,
disableTailwindBorderRadius: false,

// Optional: Configure paths
baseThemeJsonPath: './theme.json',
Expand Down
196 changes: 196 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ interface ThemeJsonPluginOptions {
*/
fontSizeLabels?: Record<string, string>;

/**
* Labels for border radius sizes to use in the WordPress editor.
* Keys should be size identifiers (e.g. 'sm', 'lg', 'full') and values are the human-readable labels.
* For example: { sm: 'Small', lg: 'Large', full: 'Full' }
* When provided, border radius names will be formatted as the label instead of the identifier.
*/
borderRadiusLabels?: Record<string, string>;

/**
* Whether to disable generating color palette entries in theme.json.
* When true, no color variables will be processed from the @theme block.
Expand All @@ -64,6 +72,14 @@ interface ThemeJsonPluginOptions {
* @default false
*/
disableTailwindFontSizes?: boolean;

/**
* Whether to disable generating border radius size entries in theme.json.
* When true, no border-radius variables will be processed from the @theme block.
*
* @default false
*/
disableTailwindBorderRadius?: boolean;
}

interface ColorPalette {
Expand Down Expand Up @@ -126,6 +142,26 @@ interface FontSize {
size: string;
}

interface BorderRadiusSize {
/**
* The human-readable name of the border radius size.
* This will be displayed in the WordPress editor.
*/
name: string;

/**
* The machine-readable identifier for the border radius size.
* This should be lowercase and URL-safe.
*/
slug: string;

/**
* The CSS border-radius value.
* Can be any valid CSS size unit (px, rem, em, etc).
*/
size: string;
}

interface ThemeJsonSettings {
/**
* Color settings including the color palette.
Expand All @@ -135,6 +171,25 @@ interface ThemeJsonSettings {
palette: ColorPalette[];
};

/**
* Border settings including radius size presets.
* Generated from --radius-* CSS variables in the @theme block.
*/
border?: {
/**
* Whether to enable border radius controls.
*/
radius?: boolean;

/**
* Available border radius size presets in the editor.
* Generated from --radius-* CSS variables.
*/
radiusSizes?: BorderRadiusSize[];

[key: string]: unknown;
};

/**
* Typography settings including font families and sizes.
* Generated from --font-* and --text-* CSS variables in the @theme block.
Expand Down Expand Up @@ -661,10 +716,12 @@ interface TailwindTheme {
colors?: Record<string, unknown>;
fontFamily?: Record<string, string[] | string>;
fontSize?: Record<string, string | [string, Record<string, string>]>;
borderRadius?: Record<string, string>;
extend?: {
colors?: Record<string, unknown>;
fontFamily?: Record<string, string[] | string>;
fontSize?: Record<string, string | [string, Record<string, string>]>;
borderRadius?: Record<string, string>;
};
}

Expand Down Expand Up @@ -692,6 +749,10 @@ function mergeThemeWithExtend(theme: TailwindTheme): TailwindTheme {
...theme.fontSize,
...theme.extend.fontSize,
},
borderRadius: {
...theme.borderRadius,
...theme.extend.borderRadius,
},
};
}

Expand Down Expand Up @@ -800,6 +861,70 @@ function processFontSizes(
});
}

/**
* Processes border radius sizes from Tailwind config into theme.json format
*/
function processBorderRadiusSizes(
sizes: Record<string, string>,
borderRadiusLabels?: Record<string, string>
): Array<{ name: string; slug: string; size: string }> {
return Object.entries(sizes).filter(([, value]) => isStaticRadiusValue(value)).map(([name, value]) => {
const displayName =
borderRadiusLabels && name in borderRadiusLabels
? borderRadiusLabels[name]
: name;

return {
name: displayName,
slug: name.toLowerCase(),
size: value,
};
});
}

/**
* Returns true if the value is a valid CSS border-radius preset value.
* Returns false for function-based values (var, clamp, calc) that can
* break the WordPress radius preset selector UI.
*/
function isStaticRadiusValue(value: string): boolean {
return !/\(/.test(value.trim());
}

/**
* Attempts to parse a border-radius size to rem for sorting.
* For multi-value radii (e.g. "15px 255px"), parses the first value.
* Returns null if the value cannot be parsed.
*/
function parseRadiusSizeForSort(size: string): number | null {
const firstValue = size.trim().split(/\s+/)[0];
const result = convertToRem(firstValue);
// convertToRem returns 0 for unrecognized units; distinguish from actual 0
if (result === 0 && parseFloat(firstValue) !== 0) return null;
return result;
}

/**
* Sorts border radius sizes from smallest to largest.
* Unparseable values are placed at the end in their original order.
*/
function sortBorderRadiusSizes(
sizes: BorderRadiusSize[]
): BorderRadiusSize[] {
return [...sizes].sort((a, b) => {
const sizeA = parseRadiusSizeForSort(a.size);
const sizeB = parseRadiusSizeForSort(b.size);

// If both are unparseable, preserve original order
if (sizeA === null && sizeB === null) return 0;
// Push unparseable values to the end
if (sizeA === null) return 1;
if (sizeB === null) return -1;

return sizeA - sizeB;
});
}

/**
* Loads and resolves the Tailwind configuration from the provided path
*/
Expand Down Expand Up @@ -864,12 +989,14 @@ export function wordpressThemeJson(config: ThemeJsonConfig = {}): VitePlugin {
disableTailwindColors = false,
disableTailwindFonts = false,
disableTailwindFontSizes = false,
disableTailwindBorderRadius = false,
baseThemeJsonPath = './theme.json',
outputPath = 'assets/theme.json',
cssFile = 'app.css',
shadeLabels,
fontLabels,
fontSizeLabels,
borderRadiusLabels,
} = config;

let cssContent: string | null = null;
Expand Down Expand Up @@ -1023,6 +1150,7 @@ export function wordpressThemeJson(config: ThemeJsonConfig = {}): VitePlugin {
COLOR: /--color-([^:]+):\s*([^;}]+)[;}]?/g,
FONT_FAMILY: /--font-([^:]+):\s*([^;}]+)[;}]?/g,
FONT_SIZE: /--text-([^:]+):\s*([^;}]+)[;}]?/g,
BORDER_RADIUS: /--radius-([^:]+):\s*([^;}]+)[;}]?/g,
} as const;

// Process colors from either @theme block or Tailwind config
Expand Down Expand Up @@ -1184,6 +1312,39 @@ export function wordpressThemeJson(config: ThemeJsonConfig = {}): VitePlugin {
]
: undefined;

// Process border radius sizes from either @theme block or Tailwind config
const borderRadiusEntries = !disableTailwindBorderRadius
? [
// Process @theme block border radius sizes if available
...extractVariables(
patterns.BORDER_RADIUS,
themeContent
)
.filter(
([, value]) => isStaticRadiusValue(value)
)
.map(([name, value]) => {
const displayName =
borderRadiusLabels &&
name in borderRadiusLabels
? borderRadiusLabels[name]
: name;
return {
name: displayName,
slug: name.toLowerCase(),
size: value,
};
}),
// Process Tailwind config border radius if available
...(resolvedTailwindConfig?.theme?.borderRadius
? processBorderRadiusSizes(
resolvedTailwindConfig.theme.borderRadius,
borderRadiusLabels
)
: []),
]
: undefined;

// Build theme.json
const themeJson: ThemeJson = {
__processed__: 'This file was generated using Vite',
Expand Down Expand Up @@ -1244,6 +1405,41 @@ export function wordpressThemeJson(config: ThemeJsonConfig = {}): VitePlugin {
)
),
},
...(() => {
if (disableTailwindBorderRadius) {
return baseThemeJson.settings?.border
? { border: baseThemeJson.settings.border }
: {};
}
const mergedRadiusSizes = sortBorderRadiusSizes(
[
...(baseThemeJson.settings?.border
?.radiusSizes || []),
...(borderRadiusEntries || []),
].filter(
(entry, index, self) =>
index ===
self.findIndex(
(e) => e.slug === entry.slug
)
)
);
// Only add radius settings if there are entries
if (mergedRadiusSizes.length === 0) {
return baseThemeJson.settings?.border
? { border: baseThemeJson.settings.border }
: {};
}
return {
border: {
...baseThemeJson.settings?.border,
radius:
baseThemeJson.settings?.border
?.radius ?? true,
radiusSizes: mergedRadiusSizes,
},
};
})(),
},
};

Expand Down
Loading
Loading