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