diff --git a/packages/form_builder/components/ui.json b/packages/form_builder/components/ui.json new file mode 100644 index 000000000..00b7f0c0e --- /dev/null +++ b/packages/form_builder/components/ui.json @@ -0,0 +1,433 @@ +{ + "$schema": "https://metabuilder.dev/schemas/json-script-components.schema.json", + "schemaVersion": "2.0.0", + "package": "form_builder", + "description": "Reusable form field components with built-in validation", + "components": [ + { + "id": "form_field", + "name": "FormField", + "description": "Generic form field with label, validation, and error display", + "props": [ + { + "name": "name", + "type": "string", + "required": true + }, + { + "name": "label", + "type": "string", + "required": true + }, + { + "name": "type", + "type": "string", + "default": "text", + "enum": ["text", "email", "password", "number", "tel", "url", "textarea"] + }, + { + "name": "placeholder", + "type": "string", + "required": false + }, + { + "name": "required", + "type": "boolean", + "default": false + }, + { + "name": "helperText", + "type": "string", + "required": false + }, + { + "name": "rows", + "type": "number", + "default": 3 + }, + { + "name": "value", + "type": "string", + "default": "" + } + ], + "state": [ + { + "name": "error", + "type": "string", + "default": "" + }, + { + "name": "touched", + "type": "boolean", + "default": false + } + ], + "handlers": { + "onChange": "fields.handleChange", + "onBlur": "fields.handleBlur", + "validate": "validate.validateField" + }, + "render": { + "type": "element", + "template": { + "type": "Box", + "className": "form-field", + "children": [ + { + "type": "Label", + "htmlFor": "{{name}}", + "children": [ + "{{label}}", + { + "type": "conditional", + "condition": "{{required}}", + "then": { + "type": "span", + "className": "text-red-500", + "children": " *" + } + } + ] + }, + { + "type": "conditional", + "condition": "{{type === 'textarea'}}", + "then": { + "type": "Textarea", + "id": "{{name}}", + "name": "{{name}}", + "placeholder": "{{placeholder}}", + "required": "{{required}}", + "rows": "{{rows}}", + "value": "{{value}}", + "onChange": "onChange", + "onBlur": "onBlur" + }, + "else": { + "type": "Input", + "id": "{{name}}", + "name": "{{name}}", + "type": "{{type}}", + "placeholder": "{{placeholder}}", + "required": "{{required}}", + "value": "{{value}}", + "onChange": "onChange", + "onBlur": "onBlur" + } + }, + { + "type": "conditional", + "condition": "{{helperText && !error}}", + "then": { + "type": "Text", + "variant": "caption", + "color": "secondary", + "children": "{{helperText}}" + } + }, + { + "type": "conditional", + "condition": "{{error && touched}}", + "then": { + "type": "Text", + "variant": "caption", + "color": "error", + "children": "{{error}}" + } + } + ] + } + } + }, + { + "id": "email_field", + "name": "EmailField", + "description": "Email input field with validation", + "props": [ + { + "name": "name", + "type": "string", + "default": "email" + }, + { + "name": "label", + "type": "string", + "default": "Email" + }, + { + "name": "required", + "type": "boolean", + "default": true + } + ], + "handlers": { + "validate": "validate.validateEmail" + }, + "render": { + "type": "element", + "template": { + "type": "FormField", + "name": "{{name}}", + "label": "{{label}}", + "type": "email", + "required": "{{required}}", + "placeholder": "you@example.com" + } + } + }, + { + "id": "password_field", + "name": "PasswordField", + "description": "Password input field with show/hide toggle", + "props": [ + { + "name": "name", + "type": "string", + "default": "password" + }, + { + "name": "label", + "type": "string", + "default": "Password" + }, + { + "name": "required", + "type": "boolean", + "default": true + }, + { + "name": "showStrength", + "type": "boolean", + "default": false + } + ], + "state": [ + { + "name": "showPassword", + "type": "boolean", + "default": false + } + ], + "handlers": { + "toggleVisibility": "fields.togglePasswordVisibility", + "validate": "validate.validatePassword" + }, + "render": { + "type": "element", + "template": { + "type": "Box", + "className": "password-field", + "children": [ + { + "type": "FormField", + "name": "{{name}}", + "label": "{{label}}", + "type": "{{showPassword ? 'text' : 'password'}}", + "required": "{{required}}" + }, + { + "type": "Button", + "variant": "ghost", + "size": "sm", + "onClick": "toggleVisibility", + "children": "{{showPassword ? 'Hide' : 'Show'}}" + } + ] + } + } + }, + { + "id": "search_bar", + "name": "SearchBar", + "description": "Search input with icon and clear button", + "props": [ + { + "name": "placeholder", + "type": "string", + "default": "Search..." + }, + { + "name": "value", + "type": "string", + "default": "" + } + ], + "handlers": { + "onChange": "fields.handleSearch", + "onClear": "fields.clearSearch" + }, + "render": { + "type": "element", + "template": { + "type": "Box", + "className": "search-bar", + "children": [ + { + "type": "Icon", + "name": "Search", + "size": 20 + }, + { + "type": "Input", + "type": "search", + "placeholder": "{{placeholder}}", + "value": "{{value}}", + "onChange": "onChange" + }, + { + "type": "conditional", + "condition": "{{value}}", + "then": { + "type": "Button", + "variant": "ghost", + "size": "sm", + "onClick": "onClear", + "children": [ + { + "type": "Icon", + "name": "X", + "size": 16 + } + ] + } + } + ] + } + } + }, + { + "id": "contact_form", + "name": "ContactForm", + "description": "Complete contact form with name, email, and message", + "props": [ + { + "name": "title", + "type": "string", + "default": "Contact form" + }, + { + "name": "description", + "type": "string", + "default": "Collect a name, email, and short message with simple validation." + }, + { + "name": "submitLabel", + "type": "string", + "default": "Send message" + } + ], + "state": [ + { + "name": "submitted", + "type": "boolean", + "default": false + }, + { + "name": "loading", + "type": "boolean", + "default": false + } + ], + "handlers": { + "onSubmit": "contact_form.handleSubmit", + "validate": "contact_form.validate" + }, + "render": { + "type": "element", + "template": { + "type": "Card", + "className": "max-w-xl", + "children": [ + { + "type": "CardHeader", + "children": [ + { + "type": "CardTitle", + "text": "{{title}}" + }, + { + "type": "CardDescription", + "text": "{{description}}" + } + ] + }, + { + "type": "CardContent", + "children": [ + { + "type": "conditional", + "condition": "{{!submitted}}", + "then": { + "type": "form", + "className": "space-y-4", + "onSubmit": "onSubmit", + "children": [ + { + "type": "FormField", + "name": "name", + "label": "Name", + "placeholder": "Your name", + "required": true + }, + { + "type": "FormField", + "name": "email", + "label": "Email", + "type": "email", + "placeholder": "you@example.com", + "required": true, + "helperText": "We will only use this to reply to your note." + }, + { + "type": "FormField", + "name": "message", + "label": "Message", + "type": "textarea", + "placeholder": "How can we help?", + "required": true, + "rows": 4 + }, + { + "type": "Button", + "type": "submit", + "disabled": "{{loading}}", + "children": "{{loading ? 'Sending...' : submitLabel}}" + } + ] + }, + "else": { + "type": "Box", + "className": "text-center py-8", + "children": [ + { + "type": "Icon", + "name": "CheckCircle", + "size": 48, + "color": "success" + }, + { + "type": "Text", + "variant": "h6", + "children": "Message sent" + }, + { + "type": "Text", + "variant": "body2", + "color": "secondary", + "children": "Thanks for reaching out. We will get back to you shortly." + } + ] + } + } + ] + } + ] + } + } + } + ], + "exports": { + "components": ["FormField", "EmailField", "PasswordField", "NumberField", "SearchBar", "ContactForm"] + } +} diff --git a/packages/form_builder/forms/contact-form.json b/packages/form_builder/forms/contact-form.json new file mode 100644 index 000000000..634437b74 --- /dev/null +++ b/packages/form_builder/forms/contact-form.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://metabuilder.dev/schemas/forms.schema.json", + "schemaVersion": "1.0.0", + "package": "form_builder", + "description": "Form definitions for form builder package", + "forms": [ + { + "id": "contact_form", + "name": "ContactForm", + "title": "Contact Us", + "description": "Send us a message", + "layout": "vertical", + "fields": [ + { + "name": "name", + "type": "text", + "label": "Name", + "placeholder": "Your name", + "required": true, + "validation": { + "required": "Name is required", + "minLength": 2, + "maxLength": 100, + "messages": { + "minLength": "Name must be at least 2 characters", + "maxLength": "Name cannot exceed 100 characters" + } + } + }, + { + "name": "email", + "type": "email", + "label": "Email", + "placeholder": "you@example.com", + "required": true, + "helpText": "We will only use this to reply to your note.", + "validation": { + "required": "Email is required", + "email": true, + "messages": { + "email": "Please enter a valid email address" + } + } + }, + { + "name": "message", + "type": "textarea", + "label": "Message", + "placeholder": "How can we help?", + "required": true, + "rows": 4, + "validation": { + "required": "Message is required", + "minLength": 10, + "maxLength": 1000, + "messages": { + "minLength": "Message must be at least 10 characters", + "maxLength": "Message cannot exceed 1000 characters" + } + } + } + ], + "submitButton": { + "label": "Send message", + "variant": "primary" + }, + "onSubmit": "contact_form.handleSubmit", + "successMessage": "Thanks for reaching out. We will get back to you shortly." + } + ] +} diff --git a/packages/form_builder/package.json b/packages/form_builder/package.json new file mode 100644 index 000000000..c908b023a --- /dev/null +++ b/packages/form_builder/package.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://metabuilder.dev/schemas/package-metadata.schema.json", + "packageId": "form_builder", + "name": "Form Builder", + "version": "1.0.0", + "description": "Form fields, validation, and submission handling", + "author": "MetaBuilder", + "license": "MIT", + "category": "ui", + "minLevel": 1, + "primary": false, + "dependencies": {}, + "devDependencies": { + "lua_test": "*" + }, + "exports": { + "components": [ + "FormField", + "EmailField", + "PasswordField", + "NumberField", + "SearchBar", + "ContactForm" + ], + "scripts": [ + "fields", + "validate", + "contact_form" + ] + }, + "tests": { + "scripts": [ + "tests/metadata.test.lua", + "tests/components.test.lua", + "tests/validate.test.lua", + "tests/contact_form.test.lua" + ], + "parameterized": [ + { + "parameters": "tests/metadata.cases.json" + }, + { + "parameters": "tests/components.cases.json" + }, + { + "parameters": "tests/validate.cases.json" + }, + { + "parameters": "tests/contact_form.cases.json" + } + ] + } +} diff --git a/packages/form_builder/scripts/functions.json b/packages/form_builder/scripts/functions.json new file mode 100644 index 000000000..9e884e616 --- /dev/null +++ b/packages/form_builder/scripts/functions.json @@ -0,0 +1,129 @@ +{ + "$schema": "https://metabuilder.dev/schemas/json-script.schema.json", + "schemaVersion": "2.2.0", + "package": "form_builder", + "description": "Form field handlers and validation functions", + "functions": [ + { + "id": "fields_handle_change", + "name": "handleChange", + "exported": true, + "description": "Handle field value change", + "category": "handlers", + "luaScript": "fields.lua" + }, + { + "id": "fields_handle_blur", + "name": "handleBlur", + "exported": true, + "description": "Handle field blur event", + "category": "handlers", + "luaScript": "fields.lua" + }, + { + "id": "fields_handle_search", + "name": "handleSearch", + "exported": true, + "description": "Handle search input change", + "category": "handlers", + "luaScript": "fields.lua" + }, + { + "id": "fields_clear_search", + "name": "clearSearch", + "exported": true, + "description": "Clear search input", + "category": "handlers", + "luaScript": "fields.lua" + }, + { + "id": "fields_toggle_password", + "name": "togglePasswordVisibility", + "exported": true, + "description": "Toggle password visibility", + "category": "handlers", + "luaScript": "fields.lua" + }, + { + "id": "validate_field", + "name": "validateField", + "exported": true, + "description": "Validate a single form field", + "category": "validation", + "luaScript": "validate.lua" + }, + { + "id": "validate_email", + "name": "validateEmail", + "exported": true, + "description": "Validate email address format", + "category": "validation", + "luaScript": "validate.lua" + }, + { + "id": "validate_password", + "name": "validatePassword", + "exported": true, + "description": "Validate password strength", + "category": "validation", + "luaScript": "validate.lua" + }, + { + "id": "validate_required", + "name": "validateRequired", + "exported": true, + "description": "Check if required field has value", + "category": "validation", + "luaScript": "validate.lua" + }, + { + "id": "validate_form", + "name": "validateForm", + "exported": true, + "description": "Validate entire form", + "category": "validation", + "luaScript": "validate.lua" + }, + { + "id": "contact_form_handle_submit", + "name": "handleSubmit", + "exported": true, + "description": "Handle contact form submission", + "category": "forms", + "luaScript": "contact_form.lua" + }, + { + "id": "contact_form_validate", + "name": "validate", + "exported": true, + "description": "Validate contact form", + "category": "forms", + "luaScript": "contact_form.lua" + }, + { + "id": "contact_form_create_state", + "name": "createInitialState", + "exported": true, + "description": "Create initial form state", + "category": "forms", + "luaScript": "contact_form.lua" + } + ], + "exports": { + "functions": [ + "handleChange", + "handleBlur", + "handleSearch", + "clearSearch", + "togglePasswordVisibility", + "validateField", + "validateEmail", + "validatePassword", + "validateRequired", + "validateForm", + "handleSubmit", + "validate", + "createInitialState" + ] + } +} diff --git a/packages/form_builder/storybook/config.json b/packages/form_builder/storybook/config.json new file mode 100644 index 000000000..c27a2c8dc --- /dev/null +++ b/packages/form_builder/storybook/config.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://metabuilder.dev/schemas/package-storybook.schema.json", + "featured": false, + "title": "Form Builder", + "description": "Reusable form fields with built-in validation", + "stories": [ + { + "name": "FormFields", + "render": "fields", + "description": "Various form field types with validation", + "type": "component" + }, + { + "name": "ContactForm", + "render": "contact_form", + "description": "Complete contact form example", + "type": "component" + }, + { + "name": "EmailField", + "render": "fields", + "description": "Email input with validation" + }, + { + "name": "PasswordField", + "render": "fields", + "description": "Password field with show/hide toggle" + }, + { + "name": "SearchBar", + "render": "fields", + "description": "Search input with clear button" + } + ], + "renders": { + "fields": { + "description": "Form field components showcase" + }, + "contact_form": { + "description": "Contact form with validation", + "featured": true + } + }, + "defaultContext": { + "user": { + "id": "demo-user", + "username": "demo_user", + "level": 1 + } + }, + "scripts": { + "renderFunctions": ["fields", "validate", "contact_form"], + "ignoredScripts": ["tests"] + }, + "parameters": { + "layout": "padded", + "backgrounds": { + "default": "light" + } + } +} diff --git a/packages/form_builder/styles/tokens.json b/packages/form_builder/styles/tokens.json new file mode 100644 index 000000000..858d70c4b --- /dev/null +++ b/packages/form_builder/styles/tokens.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://metabuilder.dev/schemas/package-styles.schema.json", + "schemaVersion": "2.0.0", + "colors": { + "fieldBorder": "#d1d5db", + "fieldBorderFocus": "#3b82f6", + "fieldBorderError": "#ef4444", + "fieldBackground": "#ffffff", + "fieldText": "#1f2937", + "labelText": "#374151", + "helperText": "#6b7280", + "errorText": "#ef4444" + }, + "spacing": { + "fieldPadding": "8px 12px", + "labelMargin": "0 0 4px 0", + "helperMargin": "4px 0 0 0" + }, + "borderRadius": { + "field": "6px" + } +} diff --git a/packages/form_builder/validation/validators.json b/packages/form_builder/validation/validators.json new file mode 100644 index 000000000..80be2f086 --- /dev/null +++ b/packages/form_builder/validation/validators.json @@ -0,0 +1,123 @@ +{ + "$schema": "https://metabuilder.dev/schemas/validation.schema.json", + "schemaVersion": "2.0.0", + "package": "form_builder", + "description": "Validation patterns and functions for form fields", + "patterns": { + "email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "password": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$", + "url": "^https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$", + "phone": "^[\\+]?[(]?[0-9]{3}[)]?[-\\s\\.]?[0-9]{3}[-\\s\\.]?[0-9]{4,6}$", + "zipCode": "^[0-9]{5}(?:-[0-9]{4})?$", + "username": "^[a-zA-Z0-9_-]{3,16}$" + }, + "functions": [ + { + "id": "validate_email", + "name": "validateEmail", + "description": "Validate email address format", + "params": [ + { + "name": "email", + "type": "string", + "required": true + } + ], + "returnType": "boolean", + "async": false, + "pattern": "email", + "errorMessage": "Please enter a valid email address" + }, + { + "id": "validate_password", + "name": "validatePassword", + "description": "Validate password strength (min 8 chars, uppercase, lowercase, number)", + "params": [ + { + "name": "password", + "type": "string", + "required": true + } + ], + "returnType": "boolean", + "async": false, + "pattern": "password", + "errorMessage": "Password must be at least 8 characters and contain uppercase, lowercase, and numbers" + }, + { + "id": "validate_required", + "name": "validateRequired", + "description": "Check if field has a value", + "params": [ + { + "name": "value", + "type": "any", + "required": true + } + ], + "returnType": "boolean", + "async": false, + "errorMessage": "This field is required" + }, + { + "id": "validate_min_length", + "name": "validateMinLength", + "description": "Check minimum string length", + "params": [ + { + "name": "value", + "type": "string", + "required": true + }, + { + "name": "minLength", + "type": "number", + "required": true + } + ], + "returnType": "boolean", + "async": false, + "errorMessage": "Must be at least ${minLength} characters" + }, + { + "id": "validate_max_length", + "name": "validateMaxLength", + "description": "Check maximum string length", + "params": [ + { + "name": "value", + "type": "string", + "required": true + }, + { + "name": "maxLength", + "type": "number", + "required": true + } + ], + "returnType": "boolean", + "async": false, + "errorMessage": "Must be no more than ${maxLength} characters" + }, + { + "id": "validate_url", + "name": "validateURL", + "description": "Validate URL format", + "params": [ + { + "name": "url", + "type": "string", + "required": true + } + ], + "returnType": "boolean", + "async": false, + "pattern": "url", + "errorMessage": "Please enter a valid URL" + } + ], + "exports": { + "patterns": ["email", "password", "url", "phone", "zipCode", "username"], + "functions": ["validateEmail", "validatePassword", "validateRequired", "validateMinLength", "validateMaxLength", "validateURL"] + } +} diff --git a/packages/notification_center/components/ui.json b/packages/notification_center/components/ui.json new file mode 100644 index 000000000..f15ee17cb --- /dev/null +++ b/packages/notification_center/components/ui.json @@ -0,0 +1,281 @@ +{ + "$schema": "https://metabuilder.dev/schemas/json-script-components.schema.json", + "schemaVersion": "2.0.0", + "package": "notification_center", + "description": "Notification components including toast, list, and summary", + "components": [ + { + "id": "notification_summary", + "name": "NotificationSummary", + "description": "Summary card showing notification counts by severity level", + "props": [ + { + "name": "title", + "type": "string", + "default": "Notification Summary" + }, + { + "name": "subtitle", + "type": "string", + "required": false + }, + { + "name": "totalLabel", + "type": "string", + "default": "Total" + } + ], + "state": [ + { + "name": "total", + "type": "number", + "default": 0 + }, + { + "name": "items", + "type": "array", + "default": [] + } + ], + "handlers": { + "init": "summary.prepareSummary" + }, + "render": { + "type": "element", + "template": { + "type": "Card", + "className": "space-y-4 p-4", + "children": [ + { + "type": "Box", + "className": "space-y-1", + "children": [ + { + "type": "Typography", + "variant": "overline", + "className": "tracking-[0.3em] text-muted-foreground", + "children": "{{title}}" + }, + { + "type": "Box", + "className": "flex items-baseline justify-between gap-3", + "children": [ + { + "type": "Typography", + "variant": "h3", + "className": "font-bold", + "children": "{{total}}" + }, + { + "type": "Badge", + "variant": "secondary", + "label": "{{totalLabel}}" + } + ] + } + ] + }, + { + "type": "Separator" + }, + { + "type": "List", + "dataSource": "items", + "className": "space-y-3", + "itemTemplate": { + "type": "Box", + "className": "flex items-center justify-between gap-3", + "children": [ + { + "type": "Box", + "children": [ + { + "type": "Typography", + "variant": "body2", + "fontWeight": "semibold", + "children": "{{item.label}}" + }, + { + "type": "Typography", + "variant": "caption", + "color": "textSecondary", + "children": "{{item.hint}}" + } + ] + }, + { + "type": "Box", + "className": "flex items-center gap-2", + "children": [ + { + "type": "Badge", + "variant": "outline", + "className": "{{item.classes}}", + "label": "{{item.count}}" + }, + { + "type": "Typography", + "variant": "overline", + "className": "tracking-[0.4em]", + "children": "{{item.severity}}" + } + ] + } + ] + } + } + ] + } + } + }, + { + "id": "notification_toast", + "name": "NotificationToast", + "description": "Toast notification popup", + "props": [ + { + "name": "type", + "type": "string", + "default": "info", + "enum": ["info", "success", "warning", "error"] + }, + { + "name": "title", + "type": "string", + "required": true + }, + { + "name": "message", + "type": "string", + "required": true + }, + { + "name": "duration", + "type": "number", + "default": 5000 + } + ], + "handlers": { + "onDismiss": "toast.dismiss" + }, + "render": { + "type": "element", + "template": { + "type": "Box", + "className": "toast toast-{{type}}", + "children": [ + { + "type": "Icon", + "name": "{{type === 'success' ? 'CheckCircle' : type === 'error' ? 'XCircle' : type === 'warning' ? 'AlertTriangle' : 'Info'}}", + "size": 20 + }, + { + "type": "Box", + "children": [ + { + "type": "Text", + "fontWeight": "semibold", + "children": "{{title}}" + }, + { + "type": "Text", + "variant": "caption", + "children": "{{message}}" + } + ] + }, + { + "type": "Button", + "variant": "ghost", + "size": "sm", + "onClick": "onDismiss", + "children": [ + { + "type": "Icon", + "name": "X", + "size": 16 + } + ] + } + ] + } + } + }, + { + "id": "notification_list", + "name": "NotificationList", + "description": "List of notifications with read/unread status", + "props": [ + { + "name": "notifications", + "type": "array", + "default": [] + }, + { + "name": "showUnreadOnly", + "type": "boolean", + "default": false + } + ], + "handlers": { + "markAsRead": "list.markAsRead", + "dismiss": "list.dismiss" + }, + "render": { + "type": "element", + "template": { + "type": "List", + "dataSource": "notifications", + "className": "notification-list", + "itemTemplate": { + "type": "Box", + "className": "notification-item {{item.read ? 'read' : 'unread'}}", + "children": [ + { + "type": "Icon", + "name": "{{item.icon || 'Bell'}}", + "size": 24, + "color": "{{item.type}}" + }, + { + "type": "Box", + "className": "flex-1", + "children": [ + { + "type": "Text", + "fontWeight": "{{item.read ? 'normal' : 'semibold'}}", + "children": "{{item.title}}" + }, + { + "type": "Text", + "variant": "caption", + "color": "secondary", + "children": "{{item.message}}" + }, + { + "type": "Text", + "variant": "caption", + "color": "secondary", + "children": "{{item.createdAt}}" + } + ] + }, + { + "type": "conditional", + "condition": "{{!item.read}}", + "then": { + "type": "Badge", + "variant": "dot", + "color": "primary" + } + } + ] + } + } + } + } + ], + "exports": { + "components": ["NotificationSummary", "NotificationToast", "NotificationList"] + } +} diff --git a/packages/notification_center/entities/schema.json b/packages/notification_center/entities/schema.json new file mode 100644 index 000000000..9776cef0a --- /dev/null +++ b/packages/notification_center/entities/schema.json @@ -0,0 +1,115 @@ +{ + "$schema": "https://metabuilder.dev/schemas/entities.schema.json", + "schemaVersion": "2.0.0", + "entities": [ + { + "name": "Notification", + "version": "1.0", + "description": "User notification for alerts, messages, and system events", + "primaryKey": "id", + "timestamps": false, + "softDelete": false, + "fields": { + "id": { + "type": "string", + "generated": true, + "description": "Unique identifier (CUID)" + }, + "tenantId": { + "type": "string", + "required": true, + "index": true, + "description": "Tenant identifier" + }, + "userId": { + "type": "string", + "required": true, + "index": true, + "description": "User who receives this notification" + }, + "type": { + "type": "enum", + "required": true, + "enum": ["info", "warning", "success", "error", "mention", "reply", "follow", "like", "system"], + "description": "Notification type/severity" + }, + "title": { + "type": "string", + "required": true, + "maxLength": 200, + "description": "Notification title" + }, + "message": { + "type": "string", + "required": true, + "description": "Notification message body" + }, + "icon": { + "type": "string", + "nullable": true, + "description": "Icon name for display" + }, + "read": { + "type": "boolean", + "default": false, + "index": true, + "description": "Whether notification has been read" + }, + "data": { + "type": "text", + "nullable": true, + "description": "JSON: action URLs, entity references, etc." + }, + "createdAt": { + "type": "bigint", + "required": true, + "index": true, + "description": "Creation timestamp in milliseconds" + }, + "expiresAt": { + "type": "bigint", + "nullable": true, + "index": true, + "description": "Expiration timestamp in milliseconds" + } + }, + "indexes": [ + { + "fields": ["tenantId"], + "name": "idx_notification_tenant" + }, + { + "fields": ["userId", "read"], + "name": "idx_notification_user_read" + }, + { + "fields": ["createdAt"], + "name": "idx_notification_created" + } + ], + "relations": [ + { + "name": "tenant", + "type": "belongsTo", + "entity": "Tenant", + "field": "tenantId", + "onDelete": "Cascade" + }, + { + "name": "user", + "type": "belongsTo", + "entity": "User", + "field": "userId", + "onDelete": "Cascade" + } + ], + "acl": { + "create": ["system", "admin"], + "read": ["self"], + "update": ["self"], + "delete": ["self"], + "rowLevel": "userId = currentUser.id" + } + } + ] +} diff --git a/packages/notification_center/events/handlers.json b/packages/notification_center/events/handlers.json new file mode 100644 index 000000000..53c484c3a --- /dev/null +++ b/packages/notification_center/events/handlers.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://metabuilder.dev/schemas/events.schema.json", + "schemaVersion": "1.0.0", + "package": "notification_center", + "description": "Notification event definitions and handlers", + "events": [ + { + "name": "notification.created", + "version": "1.0.0", + "description": "Fired when a new notification is created", + "payload": "Notification" + }, + { + "name": "notification.read", + "version": "1.0.0", + "description": "Fired when a notification is marked as read", + "payload": { + "notificationId": "string", + "userId": "string" + } + }, + { + "name": "notification.dismissed", + "version": "1.0.0", + "description": "Fired when a notification is dismissed", + "payload": { + "notificationId": "string", + "userId": "string" + } + } + ], + "subscribers": [ + { + "id": "show_toast_on_create", + "name": "Show Toast on Notification", + "events": ["notification.created"], + "handler": "toast.showToast", + "description": "Display toast when new notification arrives" + }, + { + "id": "update_count_on_read", + "name": "Update Count on Read", + "events": ["notification.read"], + "handler": "summary.prepareSummary", + "description": "Update notification count when marked as read" + } + ] +} diff --git a/packages/notification_center/package.json b/packages/notification_center/package.json new file mode 100644 index 000000000..234df279e --- /dev/null +++ b/packages/notification_center/package.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://metabuilder.dev/schemas/package-metadata.schema.json", + "packageId": "notification_center", + "name": "Notification Center", + "version": "1.0.0", + "description": "Notification center components and summary cards", + "author": "MetaBuilder", + "license": "MIT", + "category": "ui", + "minLevel": 1, + "primary": false, + "dependencies": {}, + "devDependencies": { + "lua_test": "*" + }, + "exports": { + "components": [ + "NotificationSummary", + "NotificationList", + "NotificationToast" + ], + "scripts": [ + "init", + "toast", + "list", + "summary" + ] + }, + "tests": { + "scripts": [ + "tests/metadata.test.lua", + "tests/components.test.lua" + ], + "parameterized": [ + { + "parameters": "tests/metadata.cases.json" + }, + { + "parameters": "tests/components.cases.json" + } + ] + } +} diff --git a/packages/notification_center/scripts/functions.json b/packages/notification_center/scripts/functions.json new file mode 100644 index 000000000..41c3f53e4 --- /dev/null +++ b/packages/notification_center/scripts/functions.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://metabuilder.dev/schemas/json-script.schema.json", + "schemaVersion": "2.2.0", + "package": "notification_center", + "description": "Notification management and display functions", + "functions": [ + { + "id": "init_load", + "name": "loadNotifications", + "exported": true, + "description": "Load user notifications", + "category": "lifecycle", + "luaScript": "init.lua" + }, + { + "id": "toast_show", + "name": "showToast", + "exported": true, + "description": "Display toast notification", + "category": "ui", + "luaScript": "toast.lua" + }, + { + "id": "toast_dismiss", + "name": "dismiss", + "exported": true, + "description": "Dismiss toast notification", + "category": "ui", + "luaScript": "toast.lua" + }, + { + "id": "list_mark_read", + "name": "markAsRead", + "exported": true, + "description": "Mark notification as read", + "category": "actions", + "luaScript": "list.lua" + }, + { + "id": "list_dismiss", + "name": "dismiss", + "exported": true, + "description": "Dismiss notification", + "category": "actions", + "luaScript": "list.lua" + }, + { + "id": "list_mark_all_read", + "name": "markAllAsRead", + "exported": true, + "description": "Mark all notifications as read", + "category": "actions", + "luaScript": "list.lua" + }, + { + "id": "summary_prepare", + "name": "prepareSummary", + "exported": true, + "description": "Prepare notification summary data", + "category": "analytics", + "luaScript": "summary.lua" + }, + { + "id": "summary_count_by_type", + "name": "countByType", + "exported": true, + "description": "Count notifications by type", + "category": "analytics", + "luaScript": "summary.lua" + } + ], + "exports": { + "functions": [ + "loadNotifications", + "showToast", + "dismiss", + "markAsRead", + "markAllAsRead", + "prepareSummary", + "countByType" + ] + } +} diff --git a/packages/notification_center/storybook/config.json b/packages/notification_center/storybook/config.json new file mode 100644 index 000000000..754ede563 --- /dev/null +++ b/packages/notification_center/storybook/config.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://metabuilder.dev/schemas/package-storybook.schema.json", + "featured": false, + "title": "Notification Center", + "description": "Notification management and display components", + "stories": [ + { + "name": "NotificationCenter", + "render": "init", + "description": "Complete notification center with list and summary" + }, + { + "name": "NotificationToast", + "render": "toast", + "description": "Toast notification popup", + "args": { + "type": "success", + "title": "Success", + "message": "Your changes have been saved" + } + }, + { + "name": "NotificationSummary", + "render": "summary", + "description": "Notification summary card" + } + ], + "renders": { + "init": { + "description": "Full notification center", + "featured": true + }, + "toast": { + "description": "Toast notifications" + }, + "summary": { + "description": "Notification summary statistics" + }, + "list": { + "description": "Notification list view" + } + }, + "defaultContext": { + "user": { + "id": "demo-user", + "username": "demo_user", + "level": 1 + } + }, + "scripts": { + "renderFunctions": ["init", "toast", "list", "summary"], + "ignoredScripts": ["tests"] + }, + "parameters": { + "layout": "padded" + } +} diff --git a/packages/notification_center/styles/tokens.json b/packages/notification_center/styles/tokens.json new file mode 100644 index 000000000..a8fb71737 --- /dev/null +++ b/packages/notification_center/styles/tokens.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://metabuilder.dev/schemas/package-styles.schema.json", + "schemaVersion": "2.0.0", + "colors": { + "notificationInfo": "#3b82f6", + "notificationSuccess": "#10b981", + "notificationWarning": "#f59e0b", + "notificationError": "#ef4444", + "notificationUnread": "#6366f1", + "notificationRead": "#9ca3af" + }, + "spacing": { + "toastPadding": "12px 16px", + "notificationItem": "12px" + }, + "shadows": { + "toast": "0 4px 12px rgba(0, 0, 0, 0.15)" + } +}