@ai-growth/nextjs
Version:
Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering
301 lines (300 loc) • 10.1 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import React, { createContext, useContext } from 'react';
// ============================================================================
// DEFAULT THEME
// ============================================================================
/**
* Default theme configuration
*/
export const defaultTheme = {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
background: '#ffffff',
surface: '#f8fafc',
text: '#1f2937',
textSecondary: '#6b7280',
accent: '#10b981',
success: '#059669',
warning: '#d97706',
error: '#dc2626',
border: '#e5e7eb',
},
typography: {
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
fontFamilyMono: '"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
lineHeight: {
tight: '1.25',
normal: '1.5',
relaxed: '1.75',
},
},
spacing: {
xs: '0.25rem',
sm: '0.5rem',
md: '1rem',
lg: '1.5rem',
xl: '2rem',
'2xl': '3rem',
'3xl': '4rem',
'4xl': '6rem',
},
borderRadius: {
none: '0',
sm: '0.125rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
full: '9999px',
},
shadows: {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
},
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
},
};
// ============================================================================
// CONTEXT IMPLEMENTATION
// ============================================================================
/**
* CMS Context - provides template and theme management
*/
const CmsContext = createContext(undefined);
/**
* CmsProvider - provides CMS configuration and template management
*/
export const CmsProvider = ({ children, config = {}, }) => {
// Merge provided config with defaults
const mergedConfig = {
templates: config.templates || {},
theme: mergeThemes(defaultTheme, config.theme || {}),
defaultContentType: config.defaultContentType || 'page',
enableThemeProvider: config.enableThemeProvider ?? true,
...(config.customRenderers && { customRenderers: config.customRenderers }),
};
// Template registry state
const [templateRegistry, setTemplateRegistry] = React.useState(mergedConfig.templates || {});
// Theme state
const [theme, setTheme] = React.useState(mergedConfig.theme || defaultTheme);
/**
* Get template component for a content type
*/
const getTemplate = React.useCallback((contentType) => {
// Check for exact match first
if (templateRegistry[contentType]) {
return templateRegistry[contentType];
}
// Check for wildcard patterns (e.g., "blog.*" for "blog.post")
const wildcardMatch = Object.keys(templateRegistry).find(key => {
if (key.includes('*')) {
const pattern = key.replace('*', '.*');
const regex = new RegExp(`^${pattern}$`);
return regex.test(contentType);
}
return false;
});
if (wildcardMatch) {
return templateRegistry[wildcardMatch];
}
// Check for parent type (e.g., "blog" for "blog.post")
const parentType = contentType.split('.')[0];
if (parentType !== contentType && templateRegistry[parentType]) {
return templateRegistry[parentType];
}
// Return null for unregistered templates - let consuming components handle fallback
return null;
}, [templateRegistry, mergedConfig.defaultContentType]);
/**
* Get current theme configuration
*/
const getTheme = React.useCallback(() => theme, [theme]);
/**
* Get custom renderers
*/
const getCustomRenderers = React.useCallback(() => {
return mergedConfig.customRenderers;
}, [mergedConfig.customRenderers]);
/**
* Check if template is registered for content type
*/
const isTemplateRegistered = React.useCallback((contentType) => {
return Boolean(templateRegistry[contentType]);
}, [templateRegistry]);
/**
* Register a new template for a content type
*/
const registerTemplate = React.useCallback((contentType, component) => {
setTemplateRegistry(prev => ({
...prev,
[contentType]: component,
}));
}, []);
/**
* Update theme configuration
*/
const updateTheme = React.useCallback((themeUpdate) => {
setTheme(prev => ({
...prev,
...themeUpdate,
colors: { ...prev.colors, ...themeUpdate.colors },
typography: { ...prev.typography, ...themeUpdate.typography },
spacing: { ...prev.spacing, ...themeUpdate.spacing },
borderRadius: { ...prev.borderRadius, ...themeUpdate.borderRadius },
shadows: { ...prev.shadows, ...themeUpdate.shadows },
breakpoints: { ...prev.breakpoints, ...themeUpdate.breakpoints },
}));
}, []);
// Context value
const contextValue = React.useMemo(() => ({
config: mergedConfig,
getTemplate,
getTheme,
getCustomRenderers,
isTemplateRegistered,
registerTemplate,
updateTheme,
}), [
mergedConfig,
getTemplate,
getTheme,
getCustomRenderers,
isTemplateRegistered,
registerTemplate,
updateTheme,
]);
return (_jsx(CmsContext.Provider, { value: contextValue, children: children }));
};
// ============================================================================
// HOOKS
// ============================================================================
/**
* Hook to access CMS context
*/
export const useCms = () => {
const context = useContext(CmsContext);
if (context === undefined) {
throw new Error('useCms must be used within a CmsProvider');
}
return context;
};
/**
* Hook to access theme configuration
*/
export const useCmsTheme = () => {
const { getTheme } = useCms();
return getTheme();
};
/**
* Hook to access template resolution
*/
export const useCmsTemplate = (contentType) => {
const { getTemplate } = useCms();
return getTemplate(contentType);
};
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Create CSS custom properties from theme
*/
export const createCssCustomProperties = (theme) => {
const cssVars = {};
// Colors
if (theme.colors) {
Object.entries(theme.colors).forEach(([key, value]) => {
if (value)
cssVars[`--cms-color-${key}`] = value;
});
}
// Typography
if (theme.typography) {
if (theme.typography.fontFamily) {
cssVars['--cms-font-family'] = theme.typography.fontFamily;
}
if (theme.typography.fontFamilyMono) {
cssVars['--cms-font-family-mono'] = theme.typography.fontFamilyMono;
}
// Font sizes
if (theme.typography.fontSize) {
Object.entries(theme.typography.fontSize).forEach(([key, value]) => {
if (value)
cssVars[`--cms-font-size-${key}`] = value;
});
}
// Font weights
if (theme.typography.fontWeight) {
Object.entries(theme.typography.fontWeight).forEach(([key, value]) => {
if (value)
cssVars[`--cms-font-weight-${key}`] = value;
});
}
// Line heights
if (theme.typography.lineHeight) {
Object.entries(theme.typography.lineHeight).forEach(([key, value]) => {
if (value)
cssVars[`--cms-line-height-${key}`] = value;
});
}
}
// Spacing
if (theme.spacing) {
Object.entries(theme.spacing).forEach(([key, value]) => {
if (value)
cssVars[`--cms-spacing-${key}`] = value;
});
}
// Border radius
if (theme.borderRadius) {
Object.entries(theme.borderRadius).forEach(([key, value]) => {
if (value)
cssVars[`--cms-border-radius-${key}`] = value;
});
}
// Shadows
if (theme.shadows) {
Object.entries(theme.shadows).forEach(([key, value]) => {
if (value)
cssVars[`--cms-shadow-${key}`] = value;
});
}
return cssVars;
};
/**
* Merge multiple theme configurations
*/
export const mergeThemes = (...themes) => {
return themes.reduce((merged, theme) => ({
...merged,
...theme,
colors: { ...merged.colors, ...theme.colors },
typography: { ...merged.typography, ...theme.typography },
spacing: { ...merged.spacing, ...theme.spacing },
borderRadius: { ...merged.borderRadius, ...theme.borderRadius },
shadows: { ...merged.shadows, ...theme.shadows },
breakpoints: { ...merged.breakpoints, ...theme.breakpoints },
}), defaultTheme);
};
export default CmsProvider;