mirror of
https://github.com/johndoe6345789/postgres.git
synced 2026-04-24 13:55:00 +00:00
Add component props definitions with validation and type checking
Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
572
docs/COMPONENT_PROPS.md
Normal file
572
docs/COMPONENT_PROPS.md
Normal file
@@ -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<string, any> }) {
|
||||
// Validate props
|
||||
const validation = validateComponentProps(name, props);
|
||||
|
||||
if (!validation.valid) {
|
||||
console.error(`Invalid props for ${name}:`, validation.errors);
|
||||
return (
|
||||
<Alert severity="error">
|
||||
<Typography variant="h6">Invalid Component Props</Typography>
|
||||
<ul>
|
||||
{validation.errors.map((error, idx) => (
|
||||
<li key={idx}>{error}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 <Component {...finalProps} />;
|
||||
}
|
||||
|
||||
// Usage
|
||||
<DynamicComponent
|
||||
name="Button"
|
||||
props={{
|
||||
text: 'Click me',
|
||||
variant: 'contained',
|
||||
onClick: handleClick,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<Box>
|
||||
<Typography variant="h6">{componentName} Props</Typography>
|
||||
|
||||
{Object.entries(schema.props).map(([propName, propDef]) => (
|
||||
<Box key={propName} sx={{ mb: 2 }}>
|
||||
<Typography variant="subtitle2">
|
||||
{propName}
|
||||
{propDef.required && <span style={{ color: 'red' }}>*</span>}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Type: {propDef.type}
|
||||
{propDef.default && ` • Default: ${propDef.default}`}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
{propDef.description}
|
||||
</Typography>
|
||||
|
||||
{propDef.type === 'enum' && propDef.values && (
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<Typography variant="caption">Options:</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
{propDef.values.map((value) => (
|
||||
<Chip key={value} label={value} size="small" />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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 (
|
||||
<Box>
|
||||
<Typography variant="h4">Component Reference</Typography>
|
||||
|
||||
{Object.entries(schemas).map(([componentName, schema]) => (
|
||||
<Paper key={componentName} sx={{ p: 3, mb: 3 }}>
|
||||
<Typography variant="h5">{componentName}</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Category: {schema.category}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
||||
{schema.description}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" sx={{ mt: 2 }}>Props</Typography>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Required</TableCell>
|
||||
<TableCell>Default</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Object.entries(schema.props).map(([propName, propDef]) => (
|
||||
<TableRow key={propName}>
|
||||
<TableCell>
|
||||
<code>{propName}</code>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{propDef.type === 'enum' ? (
|
||||
<Tooltip title={propDef.values?.join(', ')}>
|
||||
<span>{propDef.type}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
propDef.type
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{propDef.required ? '✓' : ''}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{propDef.default !== undefined ? (
|
||||
<code>{JSON.stringify(propDef.default)}</code>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{propDef.description}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 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<string, ComponentPropSchema>`
|
||||
|
||||
Get all component prop schemas.
|
||||
|
||||
### `getComponentPropDefinition(componentName: string, propName: string): PropDefinition | undefined`
|
||||
|
||||
Get the definition for a specific prop.
|
||||
|
||||
### `validateComponentProps(componentName: string, props: Record<string, any>): { 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!
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string, PropDefinition>;
|
||||
};
|
||||
|
||||
// Type definition for the features config structure
|
||||
type FeaturesConfig = {
|
||||
translations?: Translations;
|
||||
@@ -198,6 +212,7 @@ type FeaturesConfig = {
|
||||
relationships?: Record<string, Relationships>;
|
||||
uiViews?: Record<string, Record<string, UiView>>;
|
||||
componentTrees?: Record<string, ComponentTree>;
|
||||
componentProps?: Record<string, ComponentPropSchema>;
|
||||
features: Feature[];
|
||||
dataTypes: DataType[];
|
||||
constraintTypes?: ConstraintType[];
|
||||
@@ -334,3 +349,63 @@ export function getComponentTree(treeName: string): ComponentTree | undefined {
|
||||
export function getAllComponentTrees(): Record<string, ComponentTree> {
|
||||
return config.componentTrees || {};
|
||||
}
|
||||
|
||||
export function getComponentPropSchema(componentName: string): ComponentPropSchema | undefined {
|
||||
return config.componentProps?.[componentName];
|
||||
}
|
||||
|
||||
export function getAllComponentPropSchemas(): Record<string, ComponentPropSchema> {
|
||||
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<string, any>): { 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user