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