UNPKG

@mindmakr/gs-websdk

Version:

Web SDK for Guru SaaS System - Complete JavaScript/TypeScript SDK for building applications with dynamic schema management

640 lines (638 loc) 20.5 kB
"use strict"; /** * Theme and Layout Helper Utilities * Provides declarative theme management and layout generation from backend schemas */ Object.defineProperty(exports, "__esModule", { value: true }); exports.themeManager = exports.ThemeManager = exports.generateResponsiveLayout = exports.generateThemeCSS = exports.generateLayoutFromSchema = exports.DEFAULT_THEMES = exports.COMPONENT_MAPPING = void 0; /** * Component mapping for different field types */ exports.COMPONENT_MAPPING = { // Text inputs text: 'TextInput', email: 'EmailInput', password: 'PasswordInput', phone: 'PhoneInput', url: 'URLInput', // Text areas textarea: 'Textarea', richtext: 'RichTextEditor', // Numbers number: 'NumberInput', integer: 'IntegerInput', currency: 'CurrencyInput', percentage: 'PercentageInput', // Dates date: 'DatePicker', datetime: 'DateTimePicker', time: 'TimePicker', // Selections select: 'Select', radio: 'RadioGroup', checkbox: 'CheckboxGroup', multiselect: 'MultiSelect', // Boolean boolean: 'Switch', // Media file: 'FileUpload', image_input: 'ImageUpload', video_input: 'VideoUpload', audio_input: 'AudioUpload', // Advanced color: 'ColorPicker', reference: 'ReferenceField', rating: 'Rating', slider: 'Slider', // Layout section: 'SectionDivider', object: 'ObjectField', array: 'ArrayField' }; /** * Default theme configurations */ exports.DEFAULT_THEMES = { light: { name: 'Light', colors: { primary: '#3B82F6', secondary: '#6B7280', accent: '#8B5CF6', background: '#FFFFFF', surface: '#F9FAFB', text: '#111827', textSecondary: '#6B7280', border: '#E5E7EB', error: '#EF4444', warning: '#F59E0B', success: '#10B981', info: '#3B82F6' }, typography: { fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem' }, fontWeight: { light: 300, normal: 400, medium: 500, semibold: 600, bold: 700 } }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem' }, borderRadius: { none: '0', sm: '0.25rem', md: '0.375rem', lg: '0.5rem', 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)', lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)' } }, dark: { name: 'Dark', colors: { primary: '#60A5FA', secondary: '#9CA3AF', accent: '#A78BFA', background: '#111827', surface: '#1F2937', text: '#F9FAFB', textSecondary: '#9CA3AF', border: '#374151', error: '#F87171', warning: '#FBBF24', success: '#34D399', info: '#60A5FA' }, typography: { fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem' }, fontWeight: { light: 300, normal: 400, medium: 500, semibold: 600, bold: 700 } }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem' }, borderRadius: { none: '0', sm: '0.25rem', md: '0.375rem', lg: '0.5rem', full: '9999px' }, shadows: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.3)', md: '0 4px 6px -1px rgba(0, 0, 0, 0.4)', lg: '0 10px 15px -3px rgba(0, 0, 0, 0.4)' } }, corporate: { name: 'Corporate', colors: { primary: '#1E40AF', secondary: '#64748B', accent: '#DC2626', background: '#FFFFFF', surface: '#F8FAFC', text: '#0F172A', textSecondary: '#64748B', border: '#CBD5E1', error: '#DC2626', warning: '#D97706', success: '#059669', info: '#0284C7' }, typography: { fontFamily: '"Inter", -apple-system, BlinkMacSystemFont, sans-serif', fontSize: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem' }, fontWeight: { light: 300, normal: 400, medium: 500, semibold: 600, bold: 700 } }, spacing: { xs: '0.25rem', sm: '0.5rem', md: '1rem', lg: '1.5rem', xl: '2rem', '2xl': '3rem' }, borderRadius: { none: '0', sm: '0.125rem', md: '0.25rem', lg: '0.375rem', full: '9999px' }, shadows: { sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)', md: '0 2px 4px -1px rgba(0, 0, 0, 0.1)', lg: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' } } }; /** * Generate layout configuration from schema template */ function generateLayoutFromSchema(template, theme = exports.DEFAULT_THEMES.light) { const schema = template.schema_definition; const fieldOrder = schema['x-field-order'] || Object.keys(schema.properties || {}); const sections = []; let currentSection = { id: 'default', fields: [], layout: { columns: 12, // Default 12-column grid gap: theme.spacing.md } }; fieldOrder.forEach((fieldKey, index) => { const property = schema.properties?.[fieldKey]; if (!property) return; // Check if this is a section divider if (property.type === 'null' && property['ui:widget'] === 'section') { // Save current section if it has fields if (currentSection.fields.length > 0) { sections.push(currentSection); } // Start new section currentSection = { id: fieldKey, title: property.title, description: property.description, fields: [], layout: { columns: 12, gap: theme.spacing.md, collapsible: property['ui:collapsible'] || false, expanded: property['ui:expanded'] !== false } }; return; } // Generate field configuration const fieldType = determineFieldType(property); const component = exports.COMPONENT_MAPPING[fieldType] || 'TextInput'; // Calculate width based on layout.width or default const layoutWidth = property.layout?.width || property['ui:layout']?.width || 'full'; const width = getColumnSpan(layoutWidth); // Generate field props const fieldProps = generateFieldProps(fieldKey, property, theme); currentSection.fields.push({ id: fieldKey, component, props: fieldProps, layout: { width: `${width}/12`, // CSS Grid span height: property.layout?.height || 'auto', order: property['x-order'] || index, conditional: property.layout?.conditional } }); }); // Add the last section if (currentSection.fields.length > 0) { sections.push(currentSection); } return { sections, metadata: { title: template.name, description: template.description, version: template.version || '1.0.0', responsive: true } }; } exports.generateLayoutFromSchema = generateLayoutFromSchema; /** * Determine field type from property configuration */ function determineFieldType(property) { // Layout fields if (property.type === 'null' && property['ui:widget'] === 'section') { return 'section'; } // Widget override if (property['ui:widget']) { const widgetMap = { 'textarea': 'textarea', 'richtext': 'richtext', 'radio': 'radio', 'checkboxes': 'checkbox', 'file': 'file', 'ImageInputWidget': 'image_input', 'VideoInputWidget': 'video_input', 'AudioInputWidget': 'audio_input', 'ReferenceFieldWidget': 'reference' }; if (property['ui:widget'] && widgetMap[property['ui:widget']]) { return widgetMap[property['ui:widget']]; } } // Format-based detection if (property.format) { const formatMap = { 'email': 'email', 'password': 'password', 'phone': 'phone', 'url': 'url', 'date': 'date', 'datetime': 'datetime', 'time': 'time', 'color': 'color', 'textarea': 'textarea', 'richtext': 'richtext', 'image-url-or-upload': 'image_input', 'video-url-or-upload': 'video_input', 'audio-url-or-upload': 'audio_input', 'reference': 'reference', 'currency': 'currency', 'percentage': 'percentage', 'rating': 'rating', 'data-url': 'file' }; if (property.format && formatMap[property.format]) { return formatMap[property.format]; } } // Enum-based detection if (property.enum) { return property['ui:widget'] === 'radio' ? 'radio' : 'select'; } if (property.type === 'array' && property.items?.enum) { return property['ui:widget'] === 'checkboxes' ? 'checkbox' : 'multiselect'; } // Type-based detection if (property.type === 'boolean') return 'boolean'; if (property.type === 'number' || property.type === 'integer') return 'number'; if (property.type === 'array') return 'array'; if (property.type === 'object') return 'object'; // Default to text return 'text'; } /** * Convert layout width to column span */ function getColumnSpan(width) { const widthMap = { 'quarter': 3, '25': 3, 'half': 6, '50': 6, 'three-quarter': 9, '75': 9, 'full': 12, '100': 12 }; return widthMap[width] || 12; } /** * Generate field props based on property configuration and theme */ function generateFieldProps(fieldKey, property, theme) { const props = { name: fieldKey, label: property.title || fieldKey, placeholder: property['ui:placeholder'] || property.description, helpText: property['ui:help'] || property.description, required: false, // Will be set by parent form disabled: property['ui:disabled'] || false, // Theme-based styling theme: { colors: theme.colors, typography: theme.typography, spacing: theme.spacing, borderRadius: theme.borderRadius, shadows: theme.shadows } }; // Type-specific props switch (property.type) { case 'string': if (property.minLength) props.minLength = property.minLength; if (property.maxLength) props.maxLength = property.maxLength; if (property.pattern) props.pattern = property.pattern; break; case 'number': case 'integer': if (property.minimum !== undefined) props.min = property.minimum; if (property.maximum !== undefined) props.max = property.maximum; if (property.multipleOf) props.step = property.multipleOf; break; case 'array': if (property.minItems) props.minItems = property.minItems; if (property.maxItems) props.maxItems = property.maxItems; if (property.uniqueItems) props.unique = property.uniqueItems; break; } // Enum options if (property.enum) { props.options = property.enum.map((value, index) => ({ value, label: property.enumNames?.[index] || String(value) })); } // Array items enum if (property.type === 'array' && property.items?.enum) { props.options = property.items.enum.map((value, index) => ({ value, label: property.items.enumNames?.[index] || String(value) })); } // Default value if (property.default !== undefined) { props.defaultValue = property.default; } // UI options if (property['ui:options']) { Object.assign(props, property['ui:options']); } // Enhancement properties (from enhanced_properties) // These would be merged from template.enhanced_properties[fieldKey] return props; } /** * Generate CSS variables from theme */ function generateThemeCSS(theme) { const cssVars = [ // Colors `--color-primary: ${theme.colors.primary};`, `--color-secondary: ${theme.colors.secondary};`, `--color-accent: ${theme.colors.accent};`, `--color-background: ${theme.colors.background};`, `--color-surface: ${theme.colors.surface};`, `--color-text: ${theme.colors.text};`, `--color-text-secondary: ${theme.colors.textSecondary};`, `--color-border: ${theme.colors.border};`, `--color-error: ${theme.colors.error};`, `--color-warning: ${theme.colors.warning};`, `--color-success: ${theme.colors.success};`, `--color-info: ${theme.colors.info};`, // Typography `--font-family: ${theme.typography.fontFamily};`, `--font-size-xs: ${theme.typography.fontSize.xs};`, `--font-size-sm: ${theme.typography.fontSize.sm};`, `--font-size-base: ${theme.typography.fontSize.base};`, `--font-size-lg: ${theme.typography.fontSize.lg};`, `--font-size-xl: ${theme.typography.fontSize.xl};`, `--font-size-2xl: ${theme.typography.fontSize['2xl']};`, `--font-size-3xl: ${theme.typography.fontSize['3xl']};`, `--font-weight-light: ${theme.typography.fontWeight.light};`, `--font-weight-normal: ${theme.typography.fontWeight.normal};`, `--font-weight-medium: ${theme.typography.fontWeight.medium};`, `--font-weight-semibold: ${theme.typography.fontWeight.semibold};`, `--font-weight-bold: ${theme.typography.fontWeight.bold};`, // Spacing `--spacing-xs: ${theme.spacing.xs};`, `--spacing-sm: ${theme.spacing.sm};`, `--spacing-md: ${theme.spacing.md};`, `--spacing-lg: ${theme.spacing.lg};`, `--spacing-xl: ${theme.spacing.xl};`, `--spacing-2xl: ${theme.spacing['2xl']};`, // Border radius `--border-radius-none: ${theme.borderRadius.none};`, `--border-radius-sm: ${theme.borderRadius.sm};`, `--border-radius-md: ${theme.borderRadius.md};`, `--border-radius-lg: ${theme.borderRadius.lg};`, `--border-radius-full: ${theme.borderRadius.full};`, // Shadows `--shadow-sm: ${theme.shadows.sm};`, `--shadow-md: ${theme.shadows.md};`, `--shadow-lg: ${theme.shadows.lg};` ]; return `:root {\n ${cssVars.join('\n ')}\n}`; } exports.generateThemeCSS = generateThemeCSS; /** * Generate responsive CSS grid layout */ function generateResponsiveLayout(sections) { const css = sections.map((section, index) => { const sectionClass = `form-section-${index}`; return ` .${sectionClass} { display: grid; grid-template-columns: repeat(${section.layout.columns}, 1fr); gap: ${section.layout.gap}; margin-bottom: var(--spacing-lg); } @media (max-width: 768px) { .${sectionClass} { grid-template-columns: 1fr; } } @media (max-width: 480px) { .${sectionClass} { gap: var(--spacing-sm); margin-bottom: var(--spacing-md); } }`; }).join('\n'); return css; } exports.generateResponsiveLayout = generateResponsiveLayout; /** * Theme manager class for runtime theme switching */ class ThemeManager { constructor() { this.currentTheme = 'light'; this.themes = { ...exports.DEFAULT_THEMES }; this.callbacks = new Set(); } /** * Register a custom theme */ registerTheme(name, theme) { this.themes[name] = theme; } /** * Get available themes */ getAvailableThemes() { return Object.keys(this.themes); } /** * Get current theme */ getCurrentTheme() { const theme = this.themes[this.currentTheme]; if (!theme) { throw new Error(`Current theme '${this.currentTheme}' not found`); } return theme; } /** * Set active theme */ setTheme(themeName) { if (!this.themes[themeName]) { throw new Error(`Theme '${themeName}' not found`); } this.currentTheme = themeName; const theme = this.themes[themeName]; if (!theme) { throw new Error(`Theme '${themeName}' not found`); } // Apply CSS variables this.applyCSSVariables(theme); // Notify callbacks this.callbacks.forEach(callback => callback(theme)); } /** * Subscribe to theme changes */ onThemeChange(callback) { this.callbacks.add(callback); // Return unsubscribe function return () => { this.callbacks.delete(callback); }; } /** * Apply CSS variables to document */ applyCSSVariables(theme) { if (typeof document === 'undefined') return; const root = document.documentElement; // Apply color variables Object.entries(theme.colors).forEach(([key, value]) => { const cssVar = `--color-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`; root.style.setProperty(cssVar, value); }); // Apply typography variables root.style.setProperty('--font-family', theme.typography.fontFamily); Object.entries(theme.typography.fontSize).forEach(([key, value]) => { root.style.setProperty(`--font-size-${key}`, value); }); Object.entries(theme.typography.fontWeight).forEach(([key, value]) => { root.style.setProperty(`--font-weight-${key}`, value.toString()); }); // Apply spacing variables Object.entries(theme.spacing).forEach(([key, value]) => { root.style.setProperty(`--spacing-${key}`, value); }); // Apply border radius variables Object.entries(theme.borderRadius).forEach(([key, value]) => { root.style.setProperty(`--border-radius-${key}`, value); }); // Apply shadow variables Object.entries(theme.shadows).forEach(([key, value]) => { root.style.setProperty(`--shadow-${key}`, value); }); } } exports.ThemeManager = ThemeManager; /** * Create a default theme manager instance */ exports.themeManager = new ThemeManager(); //# sourceMappingURL=theme-helpers.js.map