UNPKG

@ai-growth/nextjs

Version:

Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering

343 lines (342 loc) 12.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeThemes = exports.createCssCustomProperties = exports.useCmsTemplate = exports.useCmsTheme = exports.useCms = exports.CmsProvider = exports.defaultTheme = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importStar(require("react")); // ============================================================================ // DEFAULT THEME // ============================================================================ /** * Default theme configuration */ exports.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 = (0, react_1.createContext)(undefined); /** * CmsProvider - provides CMS configuration and template management */ const CmsProvider = ({ children, config = {}, }) => { // Merge provided config with defaults const mergedConfig = { templates: config.templates || {}, theme: (0, exports.mergeThemes)(exports.defaultTheme, config.theme || {}), defaultContentType: config.defaultContentType || 'page', enableThemeProvider: config.enableThemeProvider ?? true, ...(config.customRenderers && { customRenderers: config.customRenderers }), }; // Template registry state const [templateRegistry, setTemplateRegistry] = react_1.default.useState(mergedConfig.templates || {}); // Theme state const [theme, setTheme] = react_1.default.useState(mergedConfig.theme || exports.defaultTheme); /** * Get template component for a content type */ const getTemplate = react_1.default.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_1.default.useCallback(() => theme, [theme]); /** * Get custom renderers */ const getCustomRenderers = react_1.default.useCallback(() => { return mergedConfig.customRenderers; }, [mergedConfig.customRenderers]); /** * Check if template is registered for content type */ const isTemplateRegistered = react_1.default.useCallback((contentType) => { return Boolean(templateRegistry[contentType]); }, [templateRegistry]); /** * Register a new template for a content type */ const registerTemplate = react_1.default.useCallback((contentType, component) => { setTemplateRegistry(prev => ({ ...prev, [contentType]: component, })); }, []); /** * Update theme configuration */ const updateTheme = react_1.default.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_1.default.useMemo(() => ({ config: mergedConfig, getTemplate, getTheme, getCustomRenderers, isTemplateRegistered, registerTemplate, updateTheme, }), [ mergedConfig, getTemplate, getTheme, getCustomRenderers, isTemplateRegistered, registerTemplate, updateTheme, ]); return ((0, jsx_runtime_1.jsx)(CmsContext.Provider, { value: contextValue, children: children })); }; exports.CmsProvider = CmsProvider; // ============================================================================ // HOOKS // ============================================================================ /** * Hook to access CMS context */ const useCms = () => { const context = (0, react_1.useContext)(CmsContext); if (context === undefined) { throw new Error('useCms must be used within a CmsProvider'); } return context; }; exports.useCms = useCms; /** * Hook to access theme configuration */ const useCmsTheme = () => { const { getTheme } = (0, exports.useCms)(); return getTheme(); }; exports.useCmsTheme = useCmsTheme; /** * Hook to access template resolution */ const useCmsTemplate = (contentType) => { const { getTemplate } = (0, exports.useCms)(); return getTemplate(contentType); }; exports.useCmsTemplate = useCmsTemplate; // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ /** * Create CSS custom properties from theme */ 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; }; exports.createCssCustomProperties = createCssCustomProperties; /** * Merge multiple theme configurations */ 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 }, }), exports.defaultTheme); }; exports.mergeThemes = mergeThemes; exports.default = exports.CmsProvider;