@ai-growth/nextjs
Version:
Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering
488 lines (487 loc) • 15.5 kB
JavaScript
/**
* Theme accessor utility class
*/
export class ThemeAccessor {
constructor(theme, cssPrefix = '--cms') {
this.theme = theme;
this.cssPrefix = cssPrefix;
}
/**
* Get color value with fallback
*/
color(key, fallback) {
const value = this.theme.colors?.[key];
const result = {
value: value || fallback || '#000000',
cssVar: `${this.cssPrefix}-color-${key}`,
};
if (fallback !== undefined) {
result.fallback = fallback;
}
return result;
}
/**
* Get spacing value with fallback
*/
spacing(key, fallback) {
const value = this.theme.spacing?.[key];
const result = {
value: value || fallback || '0',
cssVar: `${this.cssPrefix}-spacing-${key}`,
};
if (fallback !== undefined) {
result.fallback = fallback;
}
return result;
}
/**
* Get typography value with fallback
*/
typography(category, key, fallback) {
const categoryValues = this.theme.typography?.[category];
const value = categoryValues?.[key];
const result = {
value: value || fallback || '',
cssVar: `${this.cssPrefix}-${category}-${key}`,
};
if (fallback !== undefined) {
result.fallback = fallback;
}
return result;
}
/**
* Get border radius value with fallback
*/
borderRadius(key, fallback) {
const value = this.theme.borderRadius?.[key];
const result = {
value: value || fallback || '0',
cssVar: `${this.cssPrefix}-border-radius-${key}`,
};
if (fallback !== undefined) {
result.fallback = fallback;
}
return result;
}
/**
* Get shadow value with fallback
*/
shadow(key, fallback) {
const value = this.theme.shadows?.[key];
const result = {
value: value || fallback || 'none',
cssVar: `${this.cssPrefix}-shadow-${key}`,
};
if (fallback !== undefined) {
result.fallback = fallback;
}
return result;
}
/**
* Get breakpoint value with fallback
*/
breakpoint(key, fallback) {
const value = this.theme.breakpoints?.[key];
const result = {
value: value || fallback || '0px',
cssVar: `${this.cssPrefix}-breakpoint-${key}`,
};
if (fallback !== undefined) {
result.fallback = fallback;
}
return result;
}
/**
* Get CSS custom property reference
*/
cssVar(category, key) {
return `var(${this.cssPrefix}-${category}-${key})`;
}
/**
* Get CSS custom property reference with fallback
*/
cssVarWithFallback(category, key, fallback) {
return `var(${this.cssPrefix}-${category}-${key}, ${fallback})`;
}
}
/**
* CSS-in-JS Theme Adapter
*/
export class CssInJsAdapter {
constructor(theme, options = {}) {
this.theme = theme;
this.options = {
cssPrefix: '--cms',
includeFallbacks: true,
...options,
};
}
/**
* Convert CmsTheme to CSS-in-JS format
*/
toCssInJs() {
return {
colors: this.convertColors(),
typography: this.convertTypography(),
spacing: this.convertSpacing(),
borderRadius: this.convertBorderRadius(),
shadows: this.convertShadows(),
breakpoints: this.convertBreakpoints(),
zIndex: this.getDefaultZIndex(),
};
}
convertColors() {
const colors = {};
if (this.theme.colors) {
Object.entries(this.theme.colors).forEach(([key, value]) => {
if (value) {
colors[key] = this.applyTransformation(key, value);
}
});
}
return colors;
}
convertTypography() {
return {
fontFamily: this.convertObject(this.theme.typography?.fontFamily),
fontSize: this.convertObject(this.theme.typography?.fontSize),
fontWeight: this.convertObject(this.theme.typography?.fontWeight),
lineHeight: this.convertObject(this.theme.typography?.lineHeight),
};
}
convertSpacing() {
return this.convertObject(this.theme.spacing);
}
convertBorderRadius() {
return this.convertObject(this.theme.borderRadius);
}
convertShadows() {
return this.convertObject(this.theme.shadows);
}
convertBreakpoints() {
return this.convertObject(this.theme.breakpoints);
}
convertObject(obj) {
const result = {};
if (obj && typeof obj === 'object') {
Object.entries(obj).forEach(([key, value]) => {
if (value && typeof value === 'string') {
result[key] = this.applyTransformation(key, value);
}
});
}
return result;
}
applyTransformation(key, value) {
const transformation = this.options.transformations?.[key];
return transformation ? transformation(value) : value;
}
getDefaultZIndex() {
return {
auto: 0,
behind: -1,
base: 0,
dropdown: 1000,
sticky: 1020,
fixed: 1030,
modalBackdrop: 1040,
modal: 1050,
popover: 1060,
tooltip: 1070,
toast: 1080,
};
}
}
/**
* Tailwind CSS Theme Adapter
*/
export class TailwindAdapter {
constructor(theme, options = {}) {
this.theme = theme;
this.options = options;
}
/**
* Convert CmsTheme to Tailwind configuration format
*/
toTailwindConfig() {
return {
colors: this.convertColorsForTailwind(),
fontFamily: this.convertFontFamilyForTailwind(),
fontSize: this.convertFontSizeForTailwind(),
fontWeight: this.convertFontWeightForTailwind(),
spacing: this.convertSpacingForTailwind(),
borderRadius: this.convertBorderRadiusForTailwind(),
boxShadow: this.convertShadowsForTailwind(),
screens: this.convertBreakpointsForTailwind(),
zIndex: this.getDefaultZIndexForTailwind(),
};
}
convertColorsForTailwind() {
const colors = {};
if (this.theme.colors) {
Object.entries(this.theme.colors).forEach(([key, value]) => {
if (value) {
// Convert camelCase to kebab-case for Tailwind
const tailwindKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
colors[tailwindKey] = value;
}
});
}
return colors;
}
convertFontFamilyForTailwind() {
const fontFamily = {};
if (this.theme.typography?.fontFamily) {
fontFamily.sans = [this.theme.typography.fontFamily];
}
if (this.theme.typography?.fontFamilyMono) {
fontFamily.mono = [this.theme.typography.fontFamilyMono];
}
return fontFamily;
}
convertFontSizeForTailwind() {
const fontSize = {};
if (this.theme.typography?.fontSize) {
Object.entries(this.theme.typography.fontSize).forEach(([key, value]) => {
if (value) {
// Add corresponding line height from theme if available
const lineHeight = this.theme.typography?.lineHeight?.normal || '1.5';
fontSize[key] = [value, { lineHeight }];
}
});
}
return fontSize;
}
convertFontWeightForTailwind() {
const fontWeight = {};
if (this.theme.typography?.fontWeight) {
Object.entries(this.theme.typography.fontWeight).forEach(([key, value]) => {
if (value) {
fontWeight[key] = value;
}
});
}
return fontWeight;
}
convertSpacingForTailwind() {
const spacing = {};
if (this.theme.spacing) {
Object.entries(this.theme.spacing).forEach(([key, value]) => {
if (value) {
spacing[key] = value;
}
});
}
return spacing;
}
convertBorderRadiusForTailwind() {
const borderRadius = {};
if (this.theme.borderRadius) {
Object.entries(this.theme.borderRadius).forEach(([key, value]) => {
if (value) {
borderRadius[key] = value;
}
});
}
return borderRadius;
}
convertShadowsForTailwind() {
const boxShadow = {};
if (this.theme.shadows) {
Object.entries(this.theme.shadows).forEach(([key, value]) => {
if (value) {
boxShadow[key] = value;
}
});
}
return boxShadow;
}
convertBreakpointsForTailwind() {
const screens = {};
if (this.theme.breakpoints) {
Object.entries(this.theme.breakpoints).forEach(([key, value]) => {
if (value) {
screens[key] = value;
}
});
}
return screens;
}
getDefaultZIndexForTailwind() {
return {
auto: 'auto',
'0': '0',
'10': '10',
'20': '20',
'30': '30',
'40': '40',
'50': '50',
};
}
}
/**
* CSS Variables Adapter
*/
export class CssVariablesAdapter {
constructor(theme, options = {}) {
this.theme = theme;
this.options = {
cssPrefix: '--cms',
includeFallbacks: true,
...options,
};
}
/**
* Convert CmsTheme to CSS custom properties
*/
toCssVariables() {
const cssVars = {};
// Convert colors
if (this.theme.colors) {
Object.entries(this.theme.colors).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-color-${this.kebabCase(key)}`] = value;
}
});
}
// Convert typography
if (this.theme.typography) {
if (this.theme.typography.fontFamily) {
cssVars[`${this.options.cssPrefix}-font-family`] = this.theme.typography.fontFamily;
}
if (this.theme.typography.fontFamilyMono) {
cssVars[`${this.options.cssPrefix}-font-family-mono`] =
this.theme.typography.fontFamilyMono;
}
// Font sizes
if (this.theme.typography.fontSize) {
Object.entries(this.theme.typography.fontSize).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-font-size-${this.kebabCase(key)}`] = value;
}
});
}
// Font weights
if (this.theme.typography.fontWeight) {
Object.entries(this.theme.typography.fontWeight).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-font-weight-${this.kebabCase(key)}`] = value;
}
});
}
// Line heights
if (this.theme.typography.lineHeight) {
Object.entries(this.theme.typography.lineHeight).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-line-height-${this.kebabCase(key)}`] = value;
}
});
}
}
// Convert spacing
if (this.theme.spacing) {
Object.entries(this.theme.spacing).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-spacing-${this.kebabCase(key)}`] = value;
}
});
}
// Convert border radius
if (this.theme.borderRadius) {
Object.entries(this.theme.borderRadius).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-border-radius-${this.kebabCase(key)}`] = value;
}
});
}
// Convert shadows
if (this.theme.shadows) {
Object.entries(this.theme.shadows).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-shadow-${this.kebabCase(key)}`] = value;
}
});
}
// Convert breakpoints
if (this.theme.breakpoints) {
Object.entries(this.theme.breakpoints).forEach(([key, value]) => {
if (value) {
cssVars[`${this.options.cssPrefix}-breakpoint-${this.kebabCase(key)}`] = value;
}
});
}
return cssVars;
}
/**
* Generate CSS string from variables
*/
toCssString(selector = ':root') {
const variables = this.toCssVariables();
const declarations = Object.entries(variables)
.map(([property, value]) => ` ${property}: ${value};`)
.join('\n');
return `${selector} {\n${declarations}\n}`;
}
kebabCase(str) {
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
}
}
/**
* Factory functions for creating theme adapters
*/
export function createThemeAccessor(theme, cssPrefix) {
return new ThemeAccessor(theme, cssPrefix);
}
export function createCssInJsAdapter(theme, options) {
return new CssInJsAdapter(theme, options);
}
export function createTailwindAdapter(theme, options) {
return new TailwindAdapter(theme, options);
}
export function createCssVariablesAdapter(theme, options) {
return new CssVariablesAdapter(theme, options);
}
/**
* Utility functions for common theme operations
*/
export function getThemeValue(theme, path, fallback) {
const keys = path.split('.');
let current = theme;
for (const key of keys) {
if (current && typeof current === 'object' && key in current) {
current = current[key];
}
else {
return fallback;
}
}
return typeof current === 'string' ? current : fallback;
}
export function hasThemeValue(theme, path) {
return getThemeValue(theme, path) !== undefined;
}
export function mergeThemeTokens(base, override) {
return {
...base,
...override,
colors: { ...base.colors, ...override.colors },
typography: { ...base.typography, ...override.typography },
spacing: { ...base.spacing, ...override.spacing },
borderRadius: { ...base.borderRadius, ...override.borderRadius },
shadows: { ...base.shadows, ...override.shadows },
breakpoints: { ...base.breakpoints, ...override.breakpoints },
};
}
export default {
ThemeAccessor,
CssInJsAdapter,
TailwindAdapter,
CssVariablesAdapter,
createThemeAccessor,
createCssInJsAdapter,
createTailwindAdapter,
createCssVariablesAdapter,
getThemeValue,
hasThemeValue,
mergeThemeTokens,
};