From 053cb1fa845a441261bcde5fe53d087346305ed6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:43:51 +0000 Subject: [PATCH] Add component props definitions with validation and type checking Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- docs/COMPONENT_PROPS.md | 572 ++++++++++++++++++++++++++++ src/config/features.json | 656 ++++++++++++++++++++++++++++++++ src/utils/featureConfig.test.ts | 213 +++++++++++ src/utils/featureConfig.ts | 75 ++++ 4 files changed, 1516 insertions(+) create mode 100644 docs/COMPONENT_PROPS.md diff --git a/docs/COMPONENT_PROPS.md b/docs/COMPONENT_PROPS.md new file mode 100644 index 0000000..f97eefd --- /dev/null +++ b/docs/COMPONENT_PROPS.md @@ -0,0 +1,572 @@ +# Component Props Definitions + +**Define component prop schemas for validation, auto-completion, and type safety!** + +The `componentProps` section in features.json provides comprehensive prop definitions for all UI components, enabling: +- ✅ Prop validation at runtime +- ✅ Auto-completion hints in editors +- ✅ Type safety without TypeScript +- ✅ Self-documenting component APIs +- ✅ Design system consistency +- ✅ Error prevention + +## Overview + +Component prop schemas define: +- **Prop types**: string, number, boolean, array, object, function, enum, any +- **Required props**: Validation fails if missing +- **Default values**: Fallback when prop not provided +- **Enum values**: Allowed values for enum types +- **Descriptions**: Documentation for each prop +- **Categories**: Group components by purpose + +## Schema Structure + +```json +{ + "componentProps": { + "ComponentName": { + "description": "Component description", + "category": "inputs|display|layout|navigation|feedback", + "props": { + "propName": { + "type": "string|number|boolean|array|object|function|enum|any", + "description": "Prop description", + "required": true/false, + "default": "default value", + "values": ["for", "enum", "types"] + } + } + } + } +} +``` + +## Prop Type Reference + +### Basic Types + +```json +{ + "text": { + "type": "string", + "description": "Text content" + }, + "count": { + "type": "number", + "description": "Numeric value" + }, + "disabled": { + "type": "boolean", + "description": "Whether component is disabled" + }, + "items": { + "type": "array", + "description": "Array of items" + }, + "config": { + "type": "object", + "description": "Configuration object" + }, + "onClick": { + "type": "function", + "description": "Click handler" + } +} +``` + +### Enum Types + +```json +{ + "variant": { + "type": "enum", + "values": ["text", "outlined", "contained"], + "default": "text", + "description": "Button variant style" + } +} +``` + +### Required Props + +```json +{ + "columns": { + "type": "array", + "required": true, + "description": "Column definitions" + } +} +``` + +## Component Categories + +### Inputs +Components for user input: +- Button +- TextField +- Select +- Checkbox +- IconButton + +### Display +Components for displaying content: +- Typography +- DataGrid +- Icon + +### Layout +Components for page structure: +- Box +- Grid +- Paper +- Card +- AppBar +- Toolbar +- Drawer + +### Navigation +Components for navigation: +- Tabs +- Tab +- Pagination +- Drawer + +### Feedback +Components for user feedback: +- Dialog +- Alert +- CircularProgress + +## Using Component Props in Code + +### Get Component Schema + +```typescript +import { getComponentPropSchema } from '@/utils/featureConfig'; + +const schema = getComponentPropSchema('Button'); + +console.log(schema?.description); // "Material-UI Button component" +console.log(schema?.category); // "inputs" +console.log(schema?.props.variant.type); // "enum" +console.log(schema?.props.variant.values); // ["text", "outlined", "contained"] +``` + +### Get Specific Prop Definition + +```typescript +import { getComponentPropDefinition } from '@/utils/featureConfig'; + +const variantProp = getComponentPropDefinition('Button', 'variant'); + +console.log(variantProp?.type); // "enum" +console.log(variantProp?.default); // "text" +console.log(variantProp?.values); // ["text", "outlined", "contained"] +``` + +### Validate Component Props + +```typescript +import { validateComponentProps } from '@/utils/featureConfig'; + +// Valid props +const result1 = validateComponentProps('Button', { + text: 'Click me', + variant: 'contained', + color: 'primary', +}); + +console.log(result1.valid); // true +console.log(result1.errors); // [] + +// Invalid props +const result2 = validateComponentProps('Button', { + variant: 'invalid', + unknownProp: 'value', +}); + +console.log(result2.valid); // false +console.log(result2.errors); +// [ +// "Invalid value for variant: invalid. Expected one of: text, outlined, contained", +// "Unknown prop: unknownProp" +// ] + +// Missing required props +const result3 = validateComponentProps('DataGrid', { + rows: [], + // Missing required 'columns' prop +}); + +console.log(result3.valid); // false +console.log(result3.errors); // ["Missing required prop: columns"] +``` + +### Get Components by Category + +```typescript +import { getComponentsByCategory } from '@/utils/featureConfig'; + +const inputComponents = getComponentsByCategory('inputs'); +console.log(inputComponents); +// ["Button", "TextField", "Select", "Checkbox", "IconButton"] + +const layoutComponents = getComponentsByCategory('layout'); +console.log(layoutComponents); +// ["Box", "Grid", "Paper", "Card", "AppBar", "Toolbar", "Drawer"] +``` + +## Complete Example: Dynamic Component Renderer + +```typescript +import { + getComponentPropSchema, + validateComponentProps, +} from '@/utils/featureConfig'; + +function DynamicComponent({ name, props }: { name: string; props: Record }) { + // Validate props + const validation = validateComponentProps(name, props); + + if (!validation.valid) { + console.error(`Invalid props for ${name}:`, validation.errors); + return ( + + Invalid Component Props + + + ); + } + + // Get schema to apply defaults + const schema = getComponentPropSchema(name); + const finalProps = { ...props }; + + // Apply default values + if (schema) { + Object.entries(schema.props).forEach(([propName, propDef]) => { + if (!(propName in finalProps) && propDef.default !== undefined) { + finalProps[propName] = propDef.default; + } + }); + } + + // Render component + const Component = getComponent(name); + return ; +} + +// Usage + +``` + +## Example: Form Field Generator + +```typescript +import { + getComponentPropSchema, + getComponentPropDefinition, +} from '@/utils/featureConfig'; + +function FormFieldGenerator({ componentName }: { componentName: string }) { + const schema = getComponentPropSchema(componentName); + + if (!schema) return null; + + return ( + + {componentName} Props + + {Object.entries(schema.props).map(([propName, propDef]) => ( + + + {propName} + {propDef.required && *} + + + + Type: {propDef.type} + {propDef.default && ` • Default: ${propDef.default}`} + + + + {propDef.description} + + + {propDef.type === 'enum' && propDef.values && ( + + Options: + + {propDef.values.map((value) => ( + + ))} + + + )} + + ))} + + ); +} +``` + +## Example: Component Tree Validator + +```typescript +import { + getComponentTree, + validateComponentProps, +} from '@/utils/featureConfig'; + +function validateComponentTree(treeName: string): { valid: boolean; errors: Array<{ path: string; errors: string[] }> } { + const tree = getComponentTree(treeName); + + if (!tree) { + return { valid: false, errors: [{ path: 'root', errors: ['Tree not found'] }] }; + } + + const allErrors: Array<{ path: string; errors: string[] }> = []; + + function validateNode(node: any, path: string) { + const validation = validateComponentProps(node.component, node.props || {}); + + if (!validation.valid) { + allErrors.push({ path, errors: validation.errors }); + } + + if (node.children) { + node.children.forEach((child: any, idx: number) => { + validateNode(child, `${path}.children[${idx}]`); + }); + } + } + + validateNode(tree, treeName); + + return { + valid: allErrors.length === 0, + errors: allErrors, + }; +} + +// Usage +const validation = validateComponentTree('AdminDashboard'); + +if (!validation.valid) { + console.error('Component tree has validation errors:'); + validation.errors.forEach(({ path, errors }) => { + console.error(` ${path}:`, errors); + }); +} +``` + +## Example: Props Documentation Generator + +```typescript +import { getAllComponentPropSchemas } from '@/utils/featureConfig'; + +function ComponentDocumentation() { + const schemas = getAllComponentPropSchemas(); + + return ( + + Component Reference + + {Object.entries(schemas).map(([componentName, schema]) => ( + + {componentName} + + Category: {schema.category} + + + {schema.description} + + + Props + + + + Name + Type + Required + Default + Description + + + + {Object.entries(schema.props).map(([propName, propDef]) => ( + + + {propName} + + + {propDef.type === 'enum' ? ( + + {propDef.type} + + ) : ( + propDef.type + )} + + + {propDef.required ? '✓' : ''} + + + {propDef.default !== undefined ? ( + {JSON.stringify(propDef.default)} + ) : ( + '-' + )} + + {propDef.description} + + ))} + +
+
+ ))} +
+ ); +} +``` + +## Component Reference + +### Button + +Material-UI Button component + +**Category:** inputs + +**Props:** +- `text` (string): Button text content +- `variant` (enum: "text" | "outlined" | "contained"): Button variant style (default: "text") +- `color` (enum): Button color theme (default: "primary") +- `size` (enum: "small" | "medium" | "large"): Button size (default: "medium") +- `disabled` (boolean): Whether button is disabled (default: false) +- `fullWidth` (boolean): Whether button takes full width (default: false) +- `startIcon` (string): Icon name to show at start +- `endIcon` (string): Icon name to show at end +- `onClick` (function): Click event handler function name + +### TextField + +Material-UI TextField component + +**Category:** inputs + +**Props:** +- `label` (string): Field label +- `placeholder` (string): Placeholder text +- `value` (any): Field value +- `type` (enum: "text" | "email" | "password" | "number" | "tel" | "url"): Input type (default: "text") +- `variant` (enum: "standard" | "outlined" | "filled"): TextField variant (default: "outlined") +- `size` (enum: "small" | "medium"): Field size (default: "medium") +- `required` (boolean): Whether field is required (default: false) +- `disabled` (boolean): Whether field is disabled (default: false) +- `fullWidth` (boolean): Whether field takes full width (default: false) +- `multiline` (boolean): Whether field is multiline textarea (default: false) +- `rows` (number): Number of rows for multiline +- `error` (boolean): Whether field has error +- `helperText` (string): Helper text below field +- `onChange` (function): Change event handler + +### DataGrid + +Custom DataGrid component for displaying tables + +**Category:** display + +**Props:** +- `columns` (array) **required**: Column definitions +- `rows` (array) **required**: Data rows +- `loading` (boolean): Whether data is loading (default: false) +- `primaryKey` (string): Primary key field name (default: "id") +- `onEdit` (function): Edit row handler +- `onDelete` (function): Delete row handler +- `size` (enum: "small" | "medium"): Table size (default: "medium") + +### Dialog + +Material-UI Dialog component + +**Category:** feedback + +**Props:** +- `open` (boolean) **required**: Whether dialog is open +- `onClose` (function): Close handler function +- `maxWidth` (enum: "xs" | "sm" | "md" | "lg" | "xl" | false): Maximum width of dialog (default: "sm") +- `fullWidth` (boolean): Whether dialog takes full available width (default: false) +- `fullScreen` (boolean): Whether dialog is fullscreen (default: false) + +## Benefits + +1. **Runtime Validation**: Catch prop errors before rendering +2. **Self-Documenting**: Props documented in configuration +3. **Type Safety**: Without TypeScript overhead +4. **Consistency**: Enforce design system patterns +5. **Auto-Completion**: Enable editor hints +6. **Error Prevention**: Catch mistakes early +7. **Component Discovery**: Browse available components +8. **Onboarding**: New developers see prop options +9. **Testing**: Validate component usage +10. **Maintenance**: Central prop definitions + +## Best Practices + +1. **Document all props**: Include clear descriptions +2. **Mark required props**: Set `required: true` +3. **Provide defaults**: Set sensible defaults +4. **Use enums**: Limit values to valid options +5. **Categorize components**: Group by purpose +6. **Keep updated**: Sync with actual components +7. **Validate early**: Check props before rendering +8. **Generate docs**: Auto-generate reference +9. **Test schemas**: Ensure validation works +10. **Version control**: Track schema changes + +## API Reference + +### `getComponentPropSchema(componentName: string): ComponentPropSchema | undefined` + +Get the complete prop schema for a component. + +### `getAllComponentPropSchemas(): Record` + +Get all component prop schemas. + +### `getComponentPropDefinition(componentName: string, propName: string): PropDefinition | undefined` + +Get the definition for a specific prop. + +### `validateComponentProps(componentName: string, props: Record): { valid: boolean; errors: string[] }` + +Validate component props against the schema. + +### `getComponentsByCategory(category: string): string[]` + +Get all components in a specific category. + +## Conclusion + +Component prop definitions in features.json provide: +- **Type safety** without TypeScript +- **Runtime validation** to catch errors +- **Self-documenting** component APIs +- **Design system** consistency +- **Better developer experience** + +With component props, features.json becomes a complete design system definition, enabling robust, validated, configuration-driven UI development! diff --git a/src/config/features.json b/src/config/features.json index 75b9256..f9b6082 100644 --- a/src/config/features.json +++ b/src/config/features.json @@ -1135,6 +1135,662 @@ ] } }, + "componentProps": { + "Button": { + "description": "Material-UI Button component", + "category": "inputs", + "props": { + "text": { + "type": "string", + "description": "Button text content", + "required": false + }, + "variant": { + "type": "enum", + "values": ["text", "outlined", "contained"], + "default": "text", + "description": "Button variant style" + }, + "color": { + "type": "enum", + "values": ["default", "primary", "secondary", "error", "warning", "info", "success"], + "default": "primary", + "description": "Button color theme" + }, + "size": { + "type": "enum", + "values": ["small", "medium", "large"], + "default": "medium", + "description": "Button size" + }, + "disabled": { + "type": "boolean", + "default": false, + "description": "Whether button is disabled" + }, + "fullWidth": { + "type": "boolean", + "default": false, + "description": "Whether button takes full width" + }, + "startIcon": { + "type": "string", + "description": "Icon name to show at start" + }, + "endIcon": { + "type": "string", + "description": "Icon name to show at end" + }, + "onClick": { + "type": "function", + "description": "Click event handler function name" + } + } + }, + "TextField": { + "description": "Material-UI TextField component", + "category": "inputs", + "props": { + "label": { + "type": "string", + "description": "Field label" + }, + "placeholder": { + "type": "string", + "description": "Placeholder text" + }, + "value": { + "type": "any", + "description": "Field value" + }, + "type": { + "type": "enum", + "values": ["text", "email", "password", "number", "tel", "url"], + "default": "text", + "description": "Input type" + }, + "variant": { + "type": "enum", + "values": ["standard", "outlined", "filled"], + "default": "outlined", + "description": "TextField variant" + }, + "size": { + "type": "enum", + "values": ["small", "medium"], + "default": "medium", + "description": "Field size" + }, + "required": { + "type": "boolean", + "default": false, + "description": "Whether field is required" + }, + "disabled": { + "type": "boolean", + "default": false, + "description": "Whether field is disabled" + }, + "fullWidth": { + "type": "boolean", + "default": false, + "description": "Whether field takes full width" + }, + "multiline": { + "type": "boolean", + "default": false, + "description": "Whether field is multiline textarea" + }, + "rows": { + "type": "number", + "description": "Number of rows for multiline" + }, + "error": { + "type": "boolean", + "description": "Whether field has error" + }, + "helperText": { + "type": "string", + "description": "Helper text below field" + }, + "onChange": { + "type": "function", + "description": "Change event handler" + } + } + }, + "Typography": { + "description": "Material-UI Typography component", + "category": "display", + "props": { + "text": { + "type": "string", + "description": "Text content" + }, + "variant": { + "type": "enum", + "values": ["h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "caption", "button", "overline"], + "default": "body1", + "description": "Typography variant" + }, + "color": { + "type": "string", + "description": "Text color (theme color or CSS color)" + }, + "align": { + "type": "enum", + "values": ["left", "center", "right", "justify"], + "default": "left", + "description": "Text alignment" + }, + "gutterBottom": { + "type": "boolean", + "default": false, + "description": "Add bottom margin" + }, + "noWrap": { + "type": "boolean", + "default": false, + "description": "Prevent text wrapping" + } + } + }, + "Box": { + "description": "Material-UI Box component - generic container", + "category": "layout", + "props": { + "sx": { + "type": "object", + "description": "Material-UI system styles" + }, + "component": { + "type": "string", + "default": "div", + "description": "HTML element or React component to render" + } + } + }, + "Grid": { + "description": "Material-UI Grid component", + "category": "layout", + "props": { + "container": { + "type": "boolean", + "default": false, + "description": "Whether this is a grid container" + }, + "item": { + "type": "boolean", + "default": false, + "description": "Whether this is a grid item" + }, + "spacing": { + "type": "number", + "description": "Spacing between grid items (0-10)" + }, + "xs": { + "type": "number", + "description": "Grid columns on extra small screens (1-12)" + }, + "sm": { + "type": "number", + "description": "Grid columns on small screens (1-12)" + }, + "md": { + "type": "number", + "description": "Grid columns on medium screens (1-12)" + }, + "lg": { + "type": "number", + "description": "Grid columns on large screens (1-12)" + }, + "xl": { + "type": "number", + "description": "Grid columns on extra large screens (1-12)" + } + } + }, + "Paper": { + "description": "Material-UI Paper component - surface with elevation", + "category": "layout", + "props": { + "elevation": { + "type": "number", + "default": 1, + "description": "Shadow depth (0-24)" + }, + "variant": { + "type": "enum", + "values": ["elevation", "outlined"], + "default": "elevation", + "description": "Paper variant" + }, + "square": { + "type": "boolean", + "default": false, + "description": "Whether corners are square (not rounded)" + } + } + }, + "Card": { + "description": "Material-UI Card component", + "category": "layout", + "props": { + "raised": { + "type": "boolean", + "default": false, + "description": "Whether card has raised appearance" + } + } + }, + "Dialog": { + "description": "Material-UI Dialog component", + "category": "feedback", + "props": { + "open": { + "type": "boolean", + "required": true, + "description": "Whether dialog is open" + }, + "onClose": { + "type": "function", + "description": "Close handler function" + }, + "maxWidth": { + "type": "enum", + "values": ["xs", "sm", "md", "lg", "xl", false], + "default": "sm", + "description": "Maximum width of dialog" + }, + "fullWidth": { + "type": "boolean", + "default": false, + "description": "Whether dialog takes full available width" + }, + "fullScreen": { + "type": "boolean", + "default": false, + "description": "Whether dialog is fullscreen" + } + } + }, + "DataGrid": { + "description": "Custom DataGrid component for displaying tables", + "category": "display", + "props": { + "columns": { + "type": "array", + "required": true, + "description": "Column definitions" + }, + "rows": { + "type": "array", + "required": true, + "description": "Data rows" + }, + "loading": { + "type": "boolean", + "default": false, + "description": "Whether data is loading" + }, + "primaryKey": { + "type": "string", + "default": "id", + "description": "Primary key field name" + }, + "onEdit": { + "type": "function", + "description": "Edit row handler" + }, + "onDelete": { + "type": "function", + "description": "Delete row handler" + }, + "size": { + "type": "enum", + "values": ["small", "medium"], + "default": "medium", + "description": "Table size" + } + } + }, + "Pagination": { + "description": "Material-UI Pagination component", + "category": "navigation", + "props": { + "count": { + "type": "number", + "required": true, + "description": "Total number of pages" + }, + "page": { + "type": "number", + "required": true, + "description": "Current page number" + }, + "onChange": { + "type": "function", + "description": "Page change handler" + }, + "color": { + "type": "enum", + "values": ["primary", "secondary", "standard"], + "default": "standard", + "description": "Pagination color" + }, + "size": { + "type": "enum", + "values": ["small", "medium", "large"], + "default": "medium", + "description": "Pagination size" + }, + "showFirstButton": { + "type": "boolean", + "default": false, + "description": "Show first page button" + }, + "showLastButton": { + "type": "boolean", + "default": false, + "description": "Show last page button" + } + } + }, + "Tabs": { + "description": "Material-UI Tabs component", + "category": "navigation", + "props": { + "value": { + "type": "any", + "required": true, + "description": "Currently selected tab value" + }, + "onChange": { + "type": "function", + "description": "Tab change handler" + }, + "orientation": { + "type": "enum", + "values": ["horizontal", "vertical"], + "default": "horizontal", + "description": "Tab orientation" + }, + "variant": { + "type": "enum", + "values": ["standard", "scrollable", "fullWidth"], + "default": "standard", + "description": "Tabs variant" + } + } + }, + "Tab": { + "description": "Material-UI Tab component", + "category": "navigation", + "props": { + "label": { + "type": "string", + "required": true, + "description": "Tab label text" + }, + "value": { + "type": "any", + "required": true, + "description": "Tab value" + }, + "icon": { + "type": "string", + "description": "Icon name" + }, + "disabled": { + "type": "boolean", + "default": false, + "description": "Whether tab is disabled" + } + } + }, + "Icon": { + "description": "Material-UI Icon component", + "category": "display", + "props": { + "name": { + "type": "string", + "required": true, + "description": "Icon name from Material Icons" + }, + "color": { + "type": "enum", + "values": ["inherit", "primary", "secondary", "action", "disabled", "error"], + "default": "inherit", + "description": "Icon color" + }, + "fontSize": { + "type": "enum", + "values": ["small", "medium", "large", "inherit"], + "default": "medium", + "description": "Icon size" + } + } + }, + "IconButton": { + "description": "Material-UI IconButton component", + "category": "inputs", + "props": { + "icon": { + "type": "string", + "required": true, + "description": "Icon name" + }, + "color": { + "type": "enum", + "values": ["default", "primary", "secondary", "error", "warning", "info", "success"], + "default": "default", + "description": "Button color" + }, + "size": { + "type": "enum", + "values": ["small", "medium", "large"], + "default": "medium", + "description": "Button size" + }, + "disabled": { + "type": "boolean", + "default": false, + "description": "Whether button is disabled" + }, + "onClick": { + "type": "function", + "description": "Click handler" + } + } + }, + "Select": { + "description": "Material-UI Select component", + "category": "inputs", + "props": { + "label": { + "type": "string", + "description": "Select label" + }, + "value": { + "type": "any", + "description": "Selected value" + }, + "options": { + "type": "array", + "required": true, + "description": "Array of { value, label } options" + }, + "variant": { + "type": "enum", + "values": ["standard", "outlined", "filled"], + "default": "outlined", + "description": "Select variant" + }, + "fullWidth": { + "type": "boolean", + "default": false, + "description": "Whether select takes full width" + }, + "multiple": { + "type": "boolean", + "default": false, + "description": "Allow multiple selections" + }, + "onChange": { + "type": "function", + "description": "Change handler" + } + } + }, + "Checkbox": { + "description": "Material-UI Checkbox component", + "category": "inputs", + "props": { + "checked": { + "type": "boolean", + "description": "Whether checkbox is checked" + }, + "label": { + "type": "string", + "description": "Checkbox label" + }, + "disabled": { + "type": "boolean", + "default": false, + "description": "Whether checkbox is disabled" + }, + "onChange": { + "type": "function", + "description": "Change handler" + }, + "color": { + "type": "enum", + "values": ["default", "primary", "secondary"], + "default": "primary", + "description": "Checkbox color" + } + } + }, + "AppBar": { + "description": "Material-UI AppBar component", + "category": "layout", + "props": { + "position": { + "type": "enum", + "values": ["fixed", "absolute", "sticky", "static", "relative"], + "default": "fixed", + "description": "AppBar position" + }, + "color": { + "type": "enum", + "values": ["default", "primary", "secondary", "transparent", "inherit"], + "default": "primary", + "description": "AppBar color" + }, + "elevation": { + "type": "number", + "default": 4, + "description": "Shadow elevation" + } + } + }, + "Toolbar": { + "description": "Material-UI Toolbar component", + "category": "layout", + "props": { + "variant": { + "type": "enum", + "values": ["regular", "dense"], + "default": "regular", + "description": "Toolbar variant" + }, + "disableGutters": { + "type": "boolean", + "default": false, + "description": "Remove padding" + } + } + }, + "Drawer": { + "description": "Material-UI Drawer component", + "category": "navigation", + "props": { + "open": { + "type": "boolean", + "description": "Whether drawer is open" + }, + "variant": { + "type": "enum", + "values": ["permanent", "persistent", "temporary"], + "default": "temporary", + "description": "Drawer variant" + }, + "anchor": { + "type": "enum", + "values": ["left", "right", "top", "bottom"], + "default": "left", + "description": "Drawer anchor position" + }, + "onClose": { + "type": "function", + "description": "Close handler" + } + } + }, + "Alert": { + "description": "Material-UI Alert component", + "category": "feedback", + "props": { + "severity": { + "type": "enum", + "values": ["error", "warning", "info", "success"], + "default": "info", + "description": "Alert severity" + }, + "variant": { + "type": "enum", + "values": ["standard", "filled", "outlined"], + "default": "standard", + "description": "Alert variant" + }, + "onClose": { + "type": "function", + "description": "Close button handler" + }, + "text": { + "type": "string", + "description": "Alert message" + } + } + }, + "CircularProgress": { + "description": "Material-UI CircularProgress component", + "category": "feedback", + "props": { + "size": { + "type": "number", + "default": 40, + "description": "Progress size in pixels" + }, + "color": { + "type": "enum", + "values": ["primary", "secondary", "inherit"], + "default": "primary", + "description": "Progress color" + }, + "variant": { + "type": "enum", + "values": ["determinate", "indeterminate"], + "default": "indeterminate", + "description": "Progress variant" + }, + "value": { + "type": "number", + "description": "Progress value (0-100) for determinate" + } + } + } + }, "features": [ { "id": "database-crud", diff --git a/src/utils/featureConfig.test.ts b/src/utils/featureConfig.test.ts index e27e93b..efd3807 100644 --- a/src/utils/featureConfig.test.ts +++ b/src/utils/featureConfig.test.ts @@ -28,6 +28,11 @@ import { getUiView, getComponentTree, getAllComponentTrees, + getComponentPropSchema, + getAllComponentPropSchemas, + getComponentPropDefinition, + validateComponentProps, + getComponentsByCategory, } from './featureConfig'; describe('FeatureConfig', () => { @@ -1042,4 +1047,212 @@ describe('FeatureConfig', () => { expect(trees.DashboardStatsCards).toBeDefined(); }); }); + + describe('getComponentPropSchema', () => { + it('should return prop schema for Button component', () => { + const schema = getComponentPropSchema('Button'); + + expect(schema).toBeDefined(); + expect(schema?.description).toContain('Button'); + expect(schema?.category).toBe('inputs'); + expect(schema?.props).toBeDefined(); + }); + + it('should have variant prop in Button schema', () => { + const schema = getComponentPropSchema('Button'); + + expect(schema?.props.variant).toBeDefined(); + expect(schema?.props.variant.type).toBe('enum'); + expect(schema?.props.variant.values).toContain('contained'); + }); + + it('should return prop schema for TextField component', () => { + const schema = getComponentPropSchema('TextField'); + + expect(schema).toBeDefined(); + expect(schema?.category).toBe('inputs'); + expect(schema?.props.label).toBeDefined(); + }); + + it('should return prop schema for Typography component', () => { + const schema = getComponentPropSchema('Typography'); + + expect(schema).toBeDefined(); + expect(schema?.category).toBe('display'); + expect(schema?.props.variant).toBeDefined(); + }); + + it('should return undefined for non-existent component', () => { + const schema = getComponentPropSchema('NonExistentComponent'); + + expect(schema).toBeUndefined(); + }); + }); + + describe('getAllComponentPropSchemas', () => { + it('should return all component prop schemas', () => { + const schemas = getAllComponentPropSchemas(); + + expect(schemas).toBeDefined(); + expect(typeof schemas).toBe('object'); + }); + + it('should include Button schema', () => { + const schemas = getAllComponentPropSchemas(); + + expect(schemas.Button).toBeDefined(); + }); + + it('should include TextField schema', () => { + const schemas = getAllComponentPropSchemas(); + + expect(schemas.TextField).toBeDefined(); + }); + + it('should include DataGrid schema', () => { + const schemas = getAllComponentPropSchemas(); + + expect(schemas.DataGrid).toBeDefined(); + }); + }); + + describe('getComponentPropDefinition', () => { + it('should return prop definition for Button variant', () => { + const propDef = getComponentPropDefinition('Button', 'variant'); + + expect(propDef).toBeDefined(); + expect(propDef?.type).toBe('enum'); + expect(propDef?.default).toBe('text'); + }); + + it('should return prop definition for TextField label', () => { + const propDef = getComponentPropDefinition('TextField', 'label'); + + expect(propDef).toBeDefined(); + expect(propDef?.type).toBe('string'); + }); + + it('should return prop definition for DataGrid columns', () => { + const propDef = getComponentPropDefinition('DataGrid', 'columns'); + + expect(propDef).toBeDefined(); + expect(propDef?.type).toBe('array'); + expect(propDef?.required).toBe(true); + }); + + it('should return undefined for non-existent prop', () => { + const propDef = getComponentPropDefinition('Button', 'nonExistentProp'); + + expect(propDef).toBeUndefined(); + }); + }); + + describe('validateComponentProps', () => { + it('should validate Button props successfully', () => { + const result = validateComponentProps('Button', { + text: 'Click me', + variant: 'contained', + color: 'primary', + }); + + expect(result.valid).toBe(true); + expect(result.errors.length).toBe(0); + }); + + it('should detect invalid enum value', () => { + const result = validateComponentProps('Button', { + variant: 'invalid', + }); + + expect(result.valid).toBe(false); + expect(result.errors.length).toBeGreaterThan(0); + expect(result.errors[0]).toContain('Invalid value'); + }); + + it('should detect missing required prop', () => { + const result = validateComponentProps('DataGrid', { + rows: [], + }); + + expect(result.valid).toBe(false); + expect(result.errors.some(e => e.includes('columns'))).toBe(true); + }); + + it('should detect unknown prop', () => { + const result = validateComponentProps('Button', { + unknownProp: 'value', + }); + + expect(result.valid).toBe(false); + expect(result.errors.some(e => e.includes('Unknown prop'))).toBe(true); + }); + + it('should validate TextField props', () => { + const result = validateComponentProps('TextField', { + label: 'Name', + type: 'text', + value: 'John', + }); + + expect(result.valid).toBe(true); + }); + + it('should return valid for non-existent component', () => { + const result = validateComponentProps('NonExistent', { + anyProp: 'value', + }); + + expect(result.valid).toBe(true); + }); + }); + + describe('getComponentsByCategory', () => { + it('should return all input components', () => { + const components = getComponentsByCategory('inputs'); + + expect(Array.isArray(components)).toBe(true); + expect(components).toContain('Button'); + expect(components).toContain('TextField'); + }); + + it('should return all layout components', () => { + const components = getComponentsByCategory('layout'); + + expect(Array.isArray(components)).toBe(true); + expect(components).toContain('Box'); + expect(components).toContain('Grid'); + expect(components).toContain('Paper'); + }); + + it('should return all display components', () => { + const components = getComponentsByCategory('display'); + + expect(Array.isArray(components)).toBe(true); + expect(components).toContain('Typography'); + expect(components).toContain('DataGrid'); + }); + + it('should return all navigation components', () => { + const components = getComponentsByCategory('navigation'); + + expect(Array.isArray(components)).toBe(true); + expect(components).toContain('Tabs'); + expect(components).toContain('Drawer'); + }); + + it('should return all feedback components', () => { + const components = getComponentsByCategory('feedback'); + + expect(Array.isArray(components)).toBe(true); + expect(components).toContain('Dialog'); + expect(components).toContain('Alert'); + }); + + it('should return empty array for non-existent category', () => { + const components = getComponentsByCategory('nonexistent'); + + expect(Array.isArray(components)).toBe(true); + expect(components.length).toBe(0); + }); + }); }); diff --git a/src/utils/featureConfig.ts b/src/utils/featureConfig.ts index e6bf93a..ad53c3f 100644 --- a/src/utils/featureConfig.ts +++ b/src/utils/featureConfig.ts @@ -182,6 +182,20 @@ export type ComponentNode = { export type ComponentTree = ComponentNode; +export type PropDefinition = { + type: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'function' | 'enum' | 'any'; + description: string; + required?: boolean; + default?: any; + values?: any[]; +}; + +export type ComponentPropSchema = { + description: string; + category: 'inputs' | 'display' | 'layout' | 'navigation' | 'feedback'; + props: Record; +}; + // Type definition for the features config structure type FeaturesConfig = { translations?: Translations; @@ -198,6 +212,7 @@ type FeaturesConfig = { relationships?: Record; uiViews?: Record>; componentTrees?: Record; + componentProps?: Record; features: Feature[]; dataTypes: DataType[]; constraintTypes?: ConstraintType[]; @@ -334,3 +349,63 @@ export function getComponentTree(treeName: string): ComponentTree | undefined { export function getAllComponentTrees(): Record { return config.componentTrees || {}; } + +export function getComponentPropSchema(componentName: string): ComponentPropSchema | undefined { + return config.componentProps?.[componentName]; +} + +export function getAllComponentPropSchemas(): Record { + return config.componentProps || {}; +} + +export function getComponentPropDefinition(componentName: string, propName: string): PropDefinition | undefined { + return config.componentProps?.[componentName]?.props[propName]; +} + +export function validateComponentProps(componentName: string, props: Record): { valid: boolean; errors: string[] } { + const schema = getComponentPropSchema(componentName); + + if (!schema) { + return { valid: true, errors: [] }; + } + + const errors: string[] = []; + + // Check required props + Object.entries(schema.props).forEach(([propName, propDef]) => { + if (propDef.required && !(propName in props)) { + errors.push(`Missing required prop: ${propName}`); + } + }); + + // Check prop types + Object.entries(props).forEach(([propName, propValue]) => { + const propDef = schema.props[propName]; + + if (!propDef) { + errors.push(`Unknown prop: ${propName}`); + return; + } + + // Type checking + if (propDef.type === 'enum' && propDef.values) { + if (!propDef.values.includes(propValue)) { + errors.push(`Invalid value for ${propName}: ${propValue}. Expected one of: ${propDef.values.join(', ')}`); + } + } else if (propDef.type !== 'any') { + const actualType = Array.isArray(propValue) ? 'array' : typeof propValue; + if (actualType !== propDef.type) { + errors.push(`Invalid type for ${propName}: expected ${propDef.type}, got ${actualType}`); + } + } + }); + + return { valid: errors.length === 0, errors }; +} + +export function getComponentsByCategory(category: string): string[] { + const schemas = getAllComponentPropSchemas(); + return Object.entries(schemas) + .filter(([_, schema]) => schema.category === category) + .map(([name, _]) => name); +}