ueca-react 2.0.2 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/dist/ueca-react.js +3 -1453
- package/docs/Specialized Component Factories in UECA-React.md +293 -0
- package/docs/index.md +12 -5
- package/package.json +4 -3
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Specialized Component Factories in UECA-React
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Specialized Component Factories is a powerful pattern in UECA-React that allows you to create pre-configured variants of existing components by wrapping their hooks with preset properties. This pattern maintains full UECA compliance while providing convenient shortcuts for commonly-used component configurations.
|
|
6
|
+
|
|
7
|
+
## The Pattern
|
|
8
|
+
|
|
9
|
+
A specialized factory is a custom hook that calls an existing component hook with pre-configured parameters, returning a model of the same type. This eliminates code duplication and provides type-safe convenience components.
|
|
10
|
+
|
|
11
|
+
### Basic Structure
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// Base component
|
|
15
|
+
function useBaseComponent(params?: BaseComponentParams): BaseComponentModel {
|
|
16
|
+
const struct: BaseComponentStruct = {
|
|
17
|
+
props: { /* ... */ },
|
|
18
|
+
View: () => <div>...</div>
|
|
19
|
+
};
|
|
20
|
+
const model = useUIBase(struct, params);
|
|
21
|
+
return model;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Specialized factory
|
|
25
|
+
type SpecializedParams = Omit<BaseComponentParams, "presetProp">;
|
|
26
|
+
|
|
27
|
+
function useSpecializedComponent(params?: SpecializedParams): BaseComponentModel {
|
|
28
|
+
return useBaseComponent({
|
|
29
|
+
...params,
|
|
30
|
+
presetProp: "preset-value"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SpecializedComponent = UECA.getFC(useSpecializedComponent);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Real-World Example: CloseIconButton
|
|
38
|
+
|
|
39
|
+
One of the most practical applications of this pattern is the `CloseIconButton` - a specialized variant of `IconButton` with a pre-configured close icon.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// Base IconButton component
|
|
43
|
+
type IconButtonStruct = UIBaseStruct<{
|
|
44
|
+
props: {
|
|
45
|
+
color: Palette | "inherit";
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
iconView: React.ReactNode;
|
|
48
|
+
size: "small" | "medium" | "large";
|
|
49
|
+
};
|
|
50
|
+
events: {
|
|
51
|
+
onClick: (source: IconButtonModel) => UECA.MaybePromise;
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
|
|
55
|
+
function useIconButton(params?: IconButtonParams): IconButtonModel {
|
|
56
|
+
const struct: IconButtonStruct = {
|
|
57
|
+
props: {
|
|
58
|
+
id: useIconButton.name,
|
|
59
|
+
color: "inherit",
|
|
60
|
+
disabled: false,
|
|
61
|
+
iconView: undefined,
|
|
62
|
+
size: "medium",
|
|
63
|
+
},
|
|
64
|
+
methods: {
|
|
65
|
+
click: () => {
|
|
66
|
+
if (!model.disabled && model.onClick) {
|
|
67
|
+
asyncSafe(() => model.onClick(model));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
View: () => (
|
|
72
|
+
<button
|
|
73
|
+
id={model.htmlId()}
|
|
74
|
+
className={`ueca-icon-button ueca-icon-button-${model.size}`}
|
|
75
|
+
disabled={model.disabled}
|
|
76
|
+
onClick={model.click}
|
|
77
|
+
>
|
|
78
|
+
{model.iconView}
|
|
79
|
+
</button>
|
|
80
|
+
)
|
|
81
|
+
};
|
|
82
|
+
const model = useUIBase(struct, params);
|
|
83
|
+
return model;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Specialized CloseIconButton factory
|
|
87
|
+
type CloseIconButtonParams = Omit<IconButtonParams, "iconView">;
|
|
88
|
+
|
|
89
|
+
function useCloseIconButton(params?: CloseIconButtonParams): IconButtonModel {
|
|
90
|
+
return useIconButton({
|
|
91
|
+
...params,
|
|
92
|
+
iconView: (
|
|
93
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
94
|
+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
|
95
|
+
</svg>
|
|
96
|
+
)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const CloseIconButton = UECA.getFC(useCloseIconButton);
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
IconButtonModel, IconButtonParams, useIconButton, IconButton,
|
|
104
|
+
useCloseIconButton, CloseIconButton
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Usage
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// Instead of writing this every time:
|
|
112
|
+
<IconButton
|
|
113
|
+
iconView={
|
|
114
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
115
|
+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
|
116
|
+
</svg>
|
|
117
|
+
}
|
|
118
|
+
onClick={() => model.close()}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
// You can write:
|
|
122
|
+
<CloseIconButton onClick={() => model.close()} />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Key Benefits
|
|
126
|
+
|
|
127
|
+
### 1. **Type Safety**
|
|
128
|
+
Using `Omit` prevents users from accidentally overriding the preset properties:
|
|
129
|
+
```tsx
|
|
130
|
+
type CloseIconButtonParams = Omit<IconButtonParams, "iconView">;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2. **Zero Overhead**
|
|
134
|
+
No wrapper components, no extra layers - just a convenient way to call the base hook with preset values. The returned model is identical to what the base hook returns.
|
|
135
|
+
|
|
136
|
+
### 3. **Full UECA Compliance**
|
|
137
|
+
As long as the factory hook returns a valid UECA model with the proper structure (props, children, methods, events, View), the framework handles it perfectly.
|
|
138
|
+
|
|
139
|
+
### 4. **DRY Principle**
|
|
140
|
+
All logic stays in the base component. The factory is purely declarative configuration.
|
|
141
|
+
|
|
142
|
+
### 5. **Composability**
|
|
143
|
+
You can create multiple specialized variants that all return the same model type, allowing them to be used interchangeably.
|
|
144
|
+
|
|
145
|
+
## More Examples
|
|
146
|
+
|
|
147
|
+
### Submit and Cancel Buttons
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
type SubmitButtonParams = Omit<ButtonParams, "type" | "variant" | "color">;
|
|
151
|
+
|
|
152
|
+
function useSubmitButton(params?: SubmitButtonParams): ButtonModel {
|
|
153
|
+
return useButton({
|
|
154
|
+
...params,
|
|
155
|
+
type: "submit",
|
|
156
|
+
variant: "contained",
|
|
157
|
+
color: "primary.main",
|
|
158
|
+
contentView: params?.contentView || "Submit"
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type CancelButtonParams = Omit<ButtonParams, "variant" | "color">;
|
|
163
|
+
|
|
164
|
+
function useCancelButton(params?: CancelButtonParams): ButtonModel {
|
|
165
|
+
return useButton({
|
|
166
|
+
...params,
|
|
167
|
+
variant: "outlined",
|
|
168
|
+
color: "text.secondary",
|
|
169
|
+
contentView: params?.contentView || "Cancel"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Specialized Alerts
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
function useSuccessAlert(params?: Omit<AlertParams, "severity">): AlertModel {
|
|
178
|
+
return useAlert({
|
|
179
|
+
...params,
|
|
180
|
+
severity: "success"
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function useErrorAlert(params?: Omit<AlertParams, "severity">): AlertModel {
|
|
185
|
+
return useAlert({
|
|
186
|
+
...params,
|
|
187
|
+
severity: "error"
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Icon Buttons with Common Icons
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
function useMenuIconButton(params?: Omit<IconButtonParams, "iconView">): IconButtonModel {
|
|
196
|
+
return useIconButton({
|
|
197
|
+
...params,
|
|
198
|
+
iconView: (
|
|
199
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
200
|
+
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
|
|
201
|
+
</svg>
|
|
202
|
+
)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function useDeleteIconButton(params?: Omit<IconButtonParams, "iconView">): IconButtonModel {
|
|
207
|
+
return useIconButton({
|
|
208
|
+
...params,
|
|
209
|
+
iconView: (
|
|
210
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
|
211
|
+
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
|
212
|
+
</svg>
|
|
213
|
+
)
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Best Practices
|
|
219
|
+
|
|
220
|
+
### 1. Use `Omit` for Preset Properties
|
|
221
|
+
Always remove preset properties from the params type to prevent conflicts:
|
|
222
|
+
```tsx
|
|
223
|
+
type SpecializedParams = Omit<BaseParams, "presetProp1" | "presetProp2">;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 2. Allow Override for Non-Critical Props
|
|
227
|
+
For properties like `contentView` in buttons, you can provide defaults while still allowing overrides:
|
|
228
|
+
```tsx
|
|
229
|
+
function useSubmitButton(params?: SubmitButtonParams): ButtonModel {
|
|
230
|
+
return useButton({
|
|
231
|
+
...params,
|
|
232
|
+
contentView: params?.contentView || "Submit", // Default but overridable
|
|
233
|
+
type: "submit" // Always preset, not overridable
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 3. Export Both Hook and Component
|
|
239
|
+
Follow the standard UECA pattern:
|
|
240
|
+
```tsx
|
|
241
|
+
const SpecializedComponent = UECA.getFC(useSpecializedComponent);
|
|
242
|
+
export { useSpecializedComponent, SpecializedComponent };
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 4. Keep Factories Simple
|
|
246
|
+
Specialized factories should only preset configuration - avoid adding new logic or state. Complex behavior should be handled by creating a new full component.
|
|
247
|
+
|
|
248
|
+
### 5. Document the Preset Values
|
|
249
|
+
Make it clear what properties are preset and cannot be overridden:
|
|
250
|
+
```tsx
|
|
251
|
+
/**
|
|
252
|
+
* IconButton pre-configured with a close (X) icon.
|
|
253
|
+
* The iconView property is preset and cannot be overridden.
|
|
254
|
+
*/
|
|
255
|
+
function useCloseIconButton(params?: CloseIconButtonParams): IconButtonModel {
|
|
256
|
+
// ...
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## When to Use This Pattern
|
|
261
|
+
|
|
262
|
+
### ✅ Good Use Cases
|
|
263
|
+
- Common icon button variants (close, menu, delete, etc.)
|
|
264
|
+
- Form buttons with standard styling (submit, cancel, save)
|
|
265
|
+
- Alert/notification variants with preset severity
|
|
266
|
+
- Text fields with specific input types (email, password, phone)
|
|
267
|
+
- Dialog variants with preset configurations
|
|
268
|
+
|
|
269
|
+
### ❌ When Not to Use
|
|
270
|
+
- When the component needs additional state or logic
|
|
271
|
+
- When you need to modify the component's behavior, not just configuration
|
|
272
|
+
- When the preset is only used once or twice in the application
|
|
273
|
+
- When the factory would need to preset too many properties (create a full component instead)
|
|
274
|
+
|
|
275
|
+
## Relationship with Component Extension
|
|
276
|
+
|
|
277
|
+
This pattern differs from Component Extension (using `useXXX` with base struct):
|
|
278
|
+
|
|
279
|
+
| Specialized Factory | Component Extension |
|
|
280
|
+
|---------------------|---------------------|
|
|
281
|
+
| Calls existing hook with preset params | Creates new component extending base struct |
|
|
282
|
+
| Returns base model type | Returns new, extended model type |
|
|
283
|
+
| Zero overhead | Slight overhead for extension |
|
|
284
|
+
| Cannot add new props/methods | Can add new props, methods, events |
|
|
285
|
+
| Best for configuration variants | Best for behavioral extensions |
|
|
286
|
+
|
|
287
|
+
## Conclusion
|
|
288
|
+
|
|
289
|
+
Specialized Component Factories provide a UECA-compliant way to create convenient, type-safe variants of existing components. By leveraging TypeScript's `Omit` utility type and the spread operator, you can eliminate boilerplate code while maintaining full framework compatibility.
|
|
290
|
+
|
|
291
|
+
This pattern is particularly valuable for UI component libraries where many variations of a base component are needed, but each variation is just a configuration difference rather than a behavioral difference.
|
|
292
|
+
|
|
293
|
+
Remember: If your hook returns a valid UECA model, the framework will handle it correctly - giving you flexibility to create elegant abstractions that suit your application's needs.
|
package/docs/index.md
CHANGED
|
@@ -34,11 +34,18 @@
|
|
|
34
34
|
|
|
35
35
|
### [16. Component Extension in UECA-React](/docs/Component%20Extension%20in%20UECA-React.md)
|
|
36
36
|
|
|
37
|
-
### [17.
|
|
37
|
+
### [17. Specialized Component Factories](/docs/Specialized%20Component%20Factories%20in%20UECA-React.md)
|
|
38
38
|
|
|
39
|
-
### [18.
|
|
39
|
+
### [18. Tracing](/docs/Tracing%20in%20UECA-React.md)
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
### [19. Standard Code Template](/docs/code-template.md)
|
|
42
|
+
---
|
|
42
43
|
|
|
43
|
-
## Code
|
|
44
|
-
|
|
44
|
+
## UECA-React Code Example
|
|
45
|
+
|
|
46
|
+
See UECA-React in action with a complete working application demonstrating key features including component structure, state management, message bus communication, and property bindings.
|
|
47
|
+
|
|
48
|
+
**🔗 Live Demo:** [UECA-React Application Demo](https://nekutuzov.github.io/ueca-react-app/)
|
|
49
|
+
**📂 Source Code:** [GitHub Repository](https://github.com/nekutuzov/ueca-react-app)
|
|
50
|
+
|
|
51
|
+
The demo application showcases real-world usage patterns and best practices for building scalable React applications with UECA-React.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ueca-react",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Unified Encapsulated Component Architecture for React",
|
|
5
5
|
"author": "Aleksey Suvorov <cranesoft@protonmail.com>",
|
|
6
6
|
"license": "ISC",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"type": "git",
|
|
23
23
|
"url": "https://github.com/nekutuzov/ueca-react-npm.git"
|
|
24
24
|
},
|
|
25
|
-
"homepage": "https://
|
|
25
|
+
"homepage": "https://cranesoft.net",
|
|
26
26
|
"bugs": {
|
|
27
27
|
"url": "https://github.com/nekutuzov/ueca-react-npm/issues"
|
|
28
28
|
},
|
|
@@ -63,10 +63,11 @@
|
|
|
63
63
|
"eslint-plugin-react-refresh": "^0.4.20",
|
|
64
64
|
"globals": "^16.2.0",
|
|
65
65
|
"jsdom": "^26.1.0",
|
|
66
|
+
"terser": "^5.46.0",
|
|
66
67
|
"typescript": "~5.8.3",
|
|
67
68
|
"typescript-eslint": "^8.34.1",
|
|
68
69
|
"vite": "^7.0.0",
|
|
69
70
|
"vite-plugin-dts": "^4.5.4",
|
|
70
71
|
"vitest": "^3.1.1"
|
|
71
72
|
}
|
|
72
|
-
}
|
|
73
|
+
}
|