UNPKG

@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
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;