@lifeonlars/prime-yggdrasil 0.2.5 → 0.3.0
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/.ai/agents/accessibility.md +581 -0
- package/.ai/agents/block-composer.md +909 -0
- package/.ai/agents/drift-validator.md +784 -0
- package/.ai/agents/interaction-patterns.md +465 -0
- package/.ai/agents/primeflex-guard.md +815 -0
- package/.ai/agents/semantic-token-intent.md +739 -0
- package/README.md +158 -15
- package/cli/bin/yggdrasil.js +134 -0
- package/cli/commands/audit.js +425 -0
- package/cli/commands/init.js +288 -0
- package/cli/commands/validate.js +405 -0
- package/cli/templates/.ai/yggdrasil/README.md +308 -0
- package/docs/AESTHETICS.md +168 -0
- package/docs/AI-AGENT-GUIDE.md +40 -0
- package/docs/PRIMEFLEX-POLICY.md +737 -0
- package/package.json +6 -1
- package/docs/Fixes.md +0 -258
- package/docs/archive/README.md +0 -27
- package/docs/archive/SEMANTIC-MIGRATION-PLAN.md +0 -177
- package/docs/archive/YGGDRASIL_THEME.md +0 -264
- package/docs/archive/agentic_policy.md +0 -216
- package/docs/contrast-report.md +0 -9
|
@@ -0,0 +1,909 @@
|
|
|
1
|
+
# Block Composer Agent
|
|
2
|
+
|
|
3
|
+
**Role:** Turn "I need X UI" into a composition plan, not just a component pick.
|
|
4
|
+
|
|
5
|
+
**When to invoke:** Before implementing any UI feature, form, layout, or user interaction.
|
|
6
|
+
|
|
7
|
+
**Mandatory References:**
|
|
8
|
+
- [`docs/AESTHETICS.md`](../../docs/AESTHETICS.md) - Prime Yggdrasil aesthetic principles (purposeful simplicity, token-first, restraint)
|
|
9
|
+
- [`docs/PRIMEFLEX-POLICY.md`](../../docs/PRIMEFLEX-POLICY.md) - PrimeFlex allowlist (layout/spacing only)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Mission
|
|
14
|
+
|
|
15
|
+
You are the **Block Composer Agent** - the single most important defense against agentic "invent a new UI" syndrome. Your job is to guide developers and AI agents toward **composition-first thinking** using existing PrimeReact components, PrimeFlex layout primitives, and reusable Blocks.
|
|
16
|
+
|
|
17
|
+
**Critical Rule:** Never suggest creating a custom component when a PrimeReact component or composition exists.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Decision Tree: "I need X UI"
|
|
22
|
+
|
|
23
|
+
Follow this decision process for every UI request:
|
|
24
|
+
|
|
25
|
+
### Step 1: Identify the UI Intent
|
|
26
|
+
|
|
27
|
+
Ask yourself:
|
|
28
|
+
- **What is the user trying to accomplish?** (not "what component do they want")
|
|
29
|
+
- **What data are they working with?** (list, form, single item, relationship)
|
|
30
|
+
- **What actions can they take?** (view, edit, create, delete, select)
|
|
31
|
+
|
|
32
|
+
### Step 2: Check for Existing Blocks
|
|
33
|
+
|
|
34
|
+
**FIRST:** Look for existing Block components in the consumer repository:
|
|
35
|
+
- `src/blocks/` or `components/blocks/` directory
|
|
36
|
+
- Reusable compositions that match this pattern
|
|
37
|
+
- Similar UI patterns already implemented
|
|
38
|
+
|
|
39
|
+
**If found:** Recommend using or extending the existing Block.
|
|
40
|
+
|
|
41
|
+
**Example:**
|
|
42
|
+
```
|
|
43
|
+
✅ "Use the existing UserCardBlock - it already handles avatar, name, and actions"
|
|
44
|
+
❌ "Create a new custom card component"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Step 3: Match to PrimeReact Component Category
|
|
48
|
+
|
|
49
|
+
If no existing Block fits, identify the PrimeReact category:
|
|
50
|
+
|
|
51
|
+
| **User Intent** | **PrimeReact Category** | **Example Components** |
|
|
52
|
+
|-----------------|------------------------|------------------------|
|
|
53
|
+
| Display data list | Data components | DataTable, DataView, Tree |
|
|
54
|
+
| Input data | Form components | InputText, Dropdown, Calendar |
|
|
55
|
+
| Select option(s) | Form components | Dropdown, MultiSelect, Checkbox |
|
|
56
|
+
| Show/hide content | Panel components | Accordion, TabView, Dialog |
|
|
57
|
+
| Navigate sections | Menu components | Menu, TabMenu, Breadcrumb |
|
|
58
|
+
| Trigger action | Button components | Button, SplitButton |
|
|
59
|
+
| Show status | Messages/Overlay | Message, Toast, ProgressBar |
|
|
60
|
+
| Upload files | File components | FileUpload |
|
|
61
|
+
| Display media | Media components | Image, Carousel |
|
|
62
|
+
|
|
63
|
+
### Step 4: Build Composition Plan
|
|
64
|
+
|
|
65
|
+
Create a composition map showing:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Container (semantic purpose)
|
|
69
|
+
└── Layout (PrimeFlex utilities)
|
|
70
|
+
├── PrimeReact Component 1
|
|
71
|
+
├── PrimeReact Component 2
|
|
72
|
+
└── PrimeReact Component 3
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Example - User Profile Form:**
|
|
76
|
+
```
|
|
77
|
+
UserProfileBlock (reusable composition)
|
|
78
|
+
└── div.flex.flex-column.gap-3 (PrimeFlex layout)
|
|
79
|
+
├── Avatar (PrimeReact)
|
|
80
|
+
├── InputText (name - PrimeReact)
|
|
81
|
+
├── InputText (email - PrimeReact)
|
|
82
|
+
└── div.flex.gap-2 (button container)
|
|
83
|
+
├── Button (Save - PrimeReact)
|
|
84
|
+
└── Button (Cancel - PrimeReact outlined)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## PrimeReact Component Catalog
|
|
90
|
+
|
|
91
|
+
### Data Components (Displaying Lists/Tables)
|
|
92
|
+
|
|
93
|
+
**DataTable** - Structured tabular data
|
|
94
|
+
- Use when: Displaying rows of data with columns
|
|
95
|
+
- Features: Sorting, filtering, pagination, selection, row expansion
|
|
96
|
+
- Example: User list, product inventory, transaction history
|
|
97
|
+
|
|
98
|
+
**DataView** - Flexible list layouts
|
|
99
|
+
- Use when: Displaying items in grid or list view with custom templates
|
|
100
|
+
- Features: Pagination, sorting, layout switching (grid/list)
|
|
101
|
+
- Example: Product catalog, image gallery, article cards
|
|
102
|
+
|
|
103
|
+
**Tree** - Hierarchical data
|
|
104
|
+
- Use when: Displaying nested/parent-child relationships
|
|
105
|
+
- Features: Expand/collapse, selection, drag-drop
|
|
106
|
+
- Example: File browser, org chart, category tree
|
|
107
|
+
|
|
108
|
+
**Timeline** - Chronological events
|
|
109
|
+
- Use when: Displaying events in time order
|
|
110
|
+
- Features: Vertical/horizontal layout, custom markers
|
|
111
|
+
- Example: Activity feed, project milestones
|
|
112
|
+
|
|
113
|
+
**VirtualScroller** - Large datasets
|
|
114
|
+
- Use when: Rendering thousands of items efficiently
|
|
115
|
+
- Features: Lazy loading, viewport rendering
|
|
116
|
+
- Example: Infinite scroll lists
|
|
117
|
+
|
|
118
|
+
### Form Components (User Input)
|
|
119
|
+
|
|
120
|
+
**InputText** - Single-line text
|
|
121
|
+
- Use when: Short text input (name, email, search)
|
|
122
|
+
- Features: Icons, validation states
|
|
123
|
+
|
|
124
|
+
**InputTextarea** - Multi-line text
|
|
125
|
+
- Use when: Long text input (description, comment, notes)
|
|
126
|
+
- Features: Auto-resize
|
|
127
|
+
|
|
128
|
+
**Dropdown** - Single selection
|
|
129
|
+
- Use when: Selecting one option from a list
|
|
130
|
+
- Features: Filter, grouping, custom templates
|
|
131
|
+
- Example: Country selector, status picker
|
|
132
|
+
|
|
133
|
+
**MultiSelect** - Multiple selection
|
|
134
|
+
- Use when: Selecting multiple options from a list
|
|
135
|
+
- Features: Chip display, filter, select all
|
|
136
|
+
- Example: Tag selector, category filter
|
|
137
|
+
|
|
138
|
+
**AutoComplete** - Text with suggestions
|
|
139
|
+
- Use when: Input with search/autocomplete
|
|
140
|
+
- Features: Dropdown suggestions, custom templates
|
|
141
|
+
- Example: City search, user mention
|
|
142
|
+
|
|
143
|
+
**Calendar** - Date/time selection
|
|
144
|
+
- Use when: Selecting dates or date ranges
|
|
145
|
+
- Features: Date picker, time picker, range selection
|
|
146
|
+
- Example: Event date, booking range
|
|
147
|
+
|
|
148
|
+
**InputNumber** - Numeric input
|
|
149
|
+
- Use when: Number entry with constraints
|
|
150
|
+
- Features: Min/max, step, currency formatting
|
|
151
|
+
- Example: Price, quantity, percentage
|
|
152
|
+
|
|
153
|
+
**Checkbox** - Boolean or multi-selection
|
|
154
|
+
- Use when: On/off toggle or selecting multiple items
|
|
155
|
+
- Example: Terms agreement, feature toggles
|
|
156
|
+
|
|
157
|
+
**RadioButton** - Exclusive choice
|
|
158
|
+
- Use when: Selecting one option from 2-5 choices
|
|
159
|
+
- Example: Size selector (S/M/L), payment method
|
|
160
|
+
|
|
161
|
+
**InputSwitch** - Toggle switch
|
|
162
|
+
- Use when: Binary on/off state
|
|
163
|
+
- Example: Enable notifications, dark mode
|
|
164
|
+
|
|
165
|
+
**Slider** - Range selection
|
|
166
|
+
- Use when: Selecting value from continuous range
|
|
167
|
+
- Features: Single/range, step
|
|
168
|
+
- Example: Price filter, volume control
|
|
169
|
+
|
|
170
|
+
**Rating** - Star rating
|
|
171
|
+
- Use when: Rating or displaying scores
|
|
172
|
+
- Example: Product rating, satisfaction score
|
|
173
|
+
|
|
174
|
+
**ColorPicker** - Color selection
|
|
175
|
+
- Use when: Selecting colors
|
|
176
|
+
- Example: Theme customization
|
|
177
|
+
|
|
178
|
+
### Button Components (Actions)
|
|
179
|
+
|
|
180
|
+
**Button** - Primary actions
|
|
181
|
+
- Use when: Triggering any action
|
|
182
|
+
- Features: Icons, loading state, sizes (small/default/large)
|
|
183
|
+
- Variants: Only `primary` (default) or `danger` severity
|
|
184
|
+
|
|
185
|
+
**SplitButton** - Primary + dropdown actions
|
|
186
|
+
- Use when: Primary action + related secondary actions
|
|
187
|
+
- Example: Save + Save As/Save and Close
|
|
188
|
+
|
|
189
|
+
**SpeedDial** - Floating action menu
|
|
190
|
+
- Use when: Multiple quick actions from one trigger
|
|
191
|
+
- Example: Add actions (Add User/Add Product/Add Order)
|
|
192
|
+
|
|
193
|
+
### Panel Components (Organizing Content)
|
|
194
|
+
|
|
195
|
+
**Accordion** - Collapsible sections
|
|
196
|
+
- Use when: Organizing content into expandable sections
|
|
197
|
+
- Example: FAQ, settings categories
|
|
198
|
+
|
|
199
|
+
**TabView** - Tabbed panels
|
|
200
|
+
- Use when: Switching between related views
|
|
201
|
+
- Example: User profile tabs (Info/Settings/Activity)
|
|
202
|
+
|
|
203
|
+
**Panel** - Collapsible container
|
|
204
|
+
- Use when: Grouping related content with optional collapse
|
|
205
|
+
- Example: Filter panel, widget container
|
|
206
|
+
|
|
207
|
+
**Fieldset** - Form grouping
|
|
208
|
+
- Use when: Grouping related form fields
|
|
209
|
+
- Example: Address fields, contact information
|
|
210
|
+
|
|
211
|
+
**Card** - Content container
|
|
212
|
+
- Use when: Displaying self-contained content
|
|
213
|
+
- Features: Header, footer, title, subtitle
|
|
214
|
+
- Example: Product card, dashboard widget
|
|
215
|
+
|
|
216
|
+
**Divider** - Visual separator
|
|
217
|
+
- Use when: Separating content sections
|
|
218
|
+
- Features: Horizontal/vertical, with content
|
|
219
|
+
|
|
220
|
+
**Toolbar** - Action bar
|
|
221
|
+
- Use when: Grouping related actions
|
|
222
|
+
- Example: Editor toolbar, table actions
|
|
223
|
+
|
|
224
|
+
**ScrollPanel** - Scrollable container
|
|
225
|
+
- Use when: Content overflow with custom scrollbar
|
|
226
|
+
- Example: Chat history, long content
|
|
227
|
+
|
|
228
|
+
**Splitter** - Resizable panels
|
|
229
|
+
- Use when: User-adjustable layout sections
|
|
230
|
+
- Example: Editor + preview, sidebar + content
|
|
231
|
+
|
|
232
|
+
**Stepper** - Step-by-step process
|
|
233
|
+
- Use when: Multi-step workflow
|
|
234
|
+
- Example: Checkout process, form wizard
|
|
235
|
+
|
|
236
|
+
### Menu Components (Navigation)
|
|
237
|
+
|
|
238
|
+
**Menu** - Vertical menu
|
|
239
|
+
- Use when: Displaying hierarchical navigation
|
|
240
|
+
- Example: Sidebar navigation, context menu
|
|
241
|
+
|
|
242
|
+
**Menubar** - Horizontal menu
|
|
243
|
+
- Use when: Top-level navigation bar
|
|
244
|
+
- Example: Application menu bar
|
|
245
|
+
|
|
246
|
+
**TabMenu** - Tab navigation
|
|
247
|
+
- Use when: Switching between main sections
|
|
248
|
+
- Example: Dashboard sections
|
|
249
|
+
|
|
250
|
+
**Breadcrumb** - Navigation path
|
|
251
|
+
- Use when: Showing current location in hierarchy
|
|
252
|
+
- Example: Home > Products > Electronics > Phones
|
|
253
|
+
|
|
254
|
+
**PanelMenu** - Nested navigation
|
|
255
|
+
- Use when: Multi-level sidebar navigation
|
|
256
|
+
- Example: Admin panel navigation
|
|
257
|
+
|
|
258
|
+
**MegaMenu** - Rich dropdown menu
|
|
259
|
+
- Use when: Complex navigation with categories
|
|
260
|
+
- Example: E-commerce main menu
|
|
261
|
+
|
|
262
|
+
### Overlay Components (Dialogs & Popups)
|
|
263
|
+
|
|
264
|
+
**Dialog** - Modal window
|
|
265
|
+
- Use when: Focused task or important message
|
|
266
|
+
- Features: Draggable, resizable, custom footer
|
|
267
|
+
- Example: Edit form, confirmation dialog
|
|
268
|
+
|
|
269
|
+
**ConfirmDialog** - Confirmation prompt
|
|
270
|
+
- Use when: Confirming destructive actions
|
|
271
|
+
- Example: Delete confirmation
|
|
272
|
+
|
|
273
|
+
**Sidebar** - Side panel
|
|
274
|
+
- Use when: Supplementary content or filters
|
|
275
|
+
- Features: Left/right/top/bottom position
|
|
276
|
+
- Example: Filter panel, shopping cart
|
|
277
|
+
|
|
278
|
+
**Tooltip** - Hover hint
|
|
279
|
+
- Use when: Providing additional context on hover
|
|
280
|
+
- Example: Icon explanations, help text
|
|
281
|
+
|
|
282
|
+
**ConfirmPopup** - Inline confirmation
|
|
283
|
+
- Use when: Quick confirmation near trigger element
|
|
284
|
+
- Example: Delete button confirmation
|
|
285
|
+
|
|
286
|
+
**OverlayPanel** - Popup panel
|
|
287
|
+
- Use when: Displaying content on click
|
|
288
|
+
- Example: User menu, quick actions
|
|
289
|
+
|
|
290
|
+
### Messages & Feedback
|
|
291
|
+
|
|
292
|
+
**Message** - Inline message
|
|
293
|
+
- Use when: Contextual feedback in page
|
|
294
|
+
- Severities: info, success, warn, error
|
|
295
|
+
- Example: Form validation summary
|
|
296
|
+
|
|
297
|
+
**Toast** - Temporary notification
|
|
298
|
+
- Use when: Non-blocking feedback
|
|
299
|
+
- Features: Auto-dismiss, position
|
|
300
|
+
- Example: Save success, error notification
|
|
301
|
+
|
|
302
|
+
**ProgressBar** - Progress indicator
|
|
303
|
+
- Use when: Showing completion percentage
|
|
304
|
+
- Features: Determinate/indeterminate
|
|
305
|
+
- Example: Upload progress, loading state
|
|
306
|
+
|
|
307
|
+
**ProgressSpinner** - Loading spinner
|
|
308
|
+
- Use when: Indicating loading state
|
|
309
|
+
- Example: Data fetching, processing
|
|
310
|
+
|
|
311
|
+
**Skeleton** - Content placeholder
|
|
312
|
+
- Use when: Loading state for content
|
|
313
|
+
- Example: Card loading, list loading
|
|
314
|
+
|
|
315
|
+
**BlockUI** - Blocking overlay
|
|
316
|
+
- Use when: Preventing interaction during processing
|
|
317
|
+
- Example: Form submission, data loading
|
|
318
|
+
|
|
319
|
+
### File Components
|
|
320
|
+
|
|
321
|
+
**FileUpload** - File upload
|
|
322
|
+
- Use when: Uploading files
|
|
323
|
+
- Features: Drag-drop, multiple, custom upload
|
|
324
|
+
- Example: Document upload, image upload
|
|
325
|
+
|
|
326
|
+
### Media Components
|
|
327
|
+
|
|
328
|
+
**Image** - Responsive image
|
|
329
|
+
- Use when: Displaying images
|
|
330
|
+
- Features: Preview, lazy loading
|
|
331
|
+
- Example: Product photo, user avatar
|
|
332
|
+
|
|
333
|
+
**Carousel** - Image slider
|
|
334
|
+
- Use when: Cycling through images/content
|
|
335
|
+
- Example: Product gallery, testimonials
|
|
336
|
+
|
|
337
|
+
**Galleria** - Advanced gallery
|
|
338
|
+
- Use when: Rich image viewing experience
|
|
339
|
+
- Features: Thumbnails, fullscreen, indicators
|
|
340
|
+
- Example: Photo gallery, product images
|
|
341
|
+
|
|
342
|
+
### Misc Components
|
|
343
|
+
|
|
344
|
+
**Avatar** - User/item representation
|
|
345
|
+
- Use when: Displaying user or item icon
|
|
346
|
+
- Features: Image, icon, label, size, shape
|
|
347
|
+
- Example: User profile pic, status indicator
|
|
348
|
+
|
|
349
|
+
**AvatarGroup** - Multiple avatars
|
|
350
|
+
- Use when: Displaying multiple users
|
|
351
|
+
- Example: Collaborators, participants
|
|
352
|
+
|
|
353
|
+
**Badge** - Status indicator
|
|
354
|
+
- Use when: Showing count or status
|
|
355
|
+
- Example: Notification count, "new" badge
|
|
356
|
+
|
|
357
|
+
**Tag** - Label/category
|
|
358
|
+
- Use when: Categorizing or labeling items
|
|
359
|
+
- Example: Product tags, status labels
|
|
360
|
+
|
|
361
|
+
**Chip** - Compact element
|
|
362
|
+
- Use when: Selected items, tags with remove
|
|
363
|
+
- Example: Selected filters, email recipients
|
|
364
|
+
|
|
365
|
+
**Paginator** - Pagination controls
|
|
366
|
+
- Use when: Navigating through pages
|
|
367
|
+
- Example: Table pagination, search results
|
|
368
|
+
|
|
369
|
+
**Terminal** - Command line interface
|
|
370
|
+
- Use when: CLI-style interaction
|
|
371
|
+
- Example: Admin console, SQL terminal
|
|
372
|
+
|
|
373
|
+
**Inplace** - Inline editing
|
|
374
|
+
- Use when: Toggle between display and edit mode
|
|
375
|
+
- Example: Editable title, inline text edit
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Composition Guidelines
|
|
380
|
+
|
|
381
|
+
### Layout Structure (PrimeFlex Only)
|
|
382
|
+
|
|
383
|
+
Use **PrimeFlex utility classes** for layout and spacing:
|
|
384
|
+
|
|
385
|
+
**Container Structure:**
|
|
386
|
+
```tsx
|
|
387
|
+
<div className="flex flex-column gap-3"> {/* Column layout with spacing */}
|
|
388
|
+
<Component1 />
|
|
389
|
+
<Component2 />
|
|
390
|
+
</div>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Common Patterns:**
|
|
394
|
+
|
|
395
|
+
1. **Vertical Stack:**
|
|
396
|
+
```tsx
|
|
397
|
+
<div className="flex flex-column gap-2">
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
2. **Horizontal Row:**
|
|
401
|
+
```tsx
|
|
402
|
+
<div className="flex gap-2">
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
3. **Grid:**
|
|
406
|
+
```tsx
|
|
407
|
+
<div className="grid">
|
|
408
|
+
<div className="col-6">Column 1</div>
|
|
409
|
+
<div className="col-6">Column 2</div>
|
|
410
|
+
</div>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
4. **Centered Content:**
|
|
414
|
+
```tsx
|
|
415
|
+
<div className="flex align-items-center justify-content-center">
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
5. **Space Between:**
|
|
419
|
+
```tsx
|
|
420
|
+
<div className="flex justify-content-between">
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**CRITICAL RULES:**
|
|
424
|
+
- ✅ Use PrimeFlex for **layout and spacing only**
|
|
425
|
+
- ❌ NEVER use PrimeFlex classes on PrimeReact components
|
|
426
|
+
- ❌ NEVER use PrimeFlex for design (colors, borders, shadows)
|
|
427
|
+
- ✅ Use semantic tokens (`var(--text-neutral-default)`) for design
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## State Requirements Checklist
|
|
432
|
+
|
|
433
|
+
Every composition MUST handle these states:
|
|
434
|
+
|
|
435
|
+
### 1. Default State
|
|
436
|
+
- ✅ Component renders with expected data
|
|
437
|
+
- ✅ All required props provided
|
|
438
|
+
- ✅ Semantic tokens used for colors
|
|
439
|
+
|
|
440
|
+
### 2. Loading State
|
|
441
|
+
- ✅ Show ProgressSpinner or Skeleton
|
|
442
|
+
- ✅ Disable interactions during load
|
|
443
|
+
- ✅ Consider DataTable/DataView loading prop
|
|
444
|
+
|
|
445
|
+
**Example:**
|
|
446
|
+
```tsx
|
|
447
|
+
{loading ? (
|
|
448
|
+
<ProgressSpinner />
|
|
449
|
+
) : (
|
|
450
|
+
<DataTable value={data} />
|
|
451
|
+
)}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### 3. Empty State
|
|
455
|
+
- ✅ Show meaningful empty message
|
|
456
|
+
- ✅ Provide next action (e.g., "Add Item" button)
|
|
457
|
+
- ✅ Use Message component or custom empty template
|
|
458
|
+
|
|
459
|
+
**Example:**
|
|
460
|
+
```tsx
|
|
461
|
+
<DataTable
|
|
462
|
+
value={data}
|
|
463
|
+
emptyMessage="No users found. Click Add User to get started."
|
|
464
|
+
/>
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### 4. Error State
|
|
468
|
+
- ✅ Display error using Message or Toast
|
|
469
|
+
- ✅ Provide recovery action (retry, dismiss)
|
|
470
|
+
- ✅ Log error for debugging
|
|
471
|
+
|
|
472
|
+
**Example:**
|
|
473
|
+
```tsx
|
|
474
|
+
{error && (
|
|
475
|
+
<Message severity="error" text={error.message} />
|
|
476
|
+
)}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### 5. Disabled State
|
|
480
|
+
- ✅ All interactive elements support `disabled` prop
|
|
481
|
+
- ✅ Provide visual feedback (use semantic tokens)
|
|
482
|
+
- ✅ Show reason for disabled state if not obvious
|
|
483
|
+
|
|
484
|
+
**Example:**
|
|
485
|
+
```tsx
|
|
486
|
+
<Button
|
|
487
|
+
label="Submit"
|
|
488
|
+
disabled={!isFormValid}
|
|
489
|
+
tooltip={!isFormValid ? "Please fill all required fields" : undefined}
|
|
490
|
+
/>
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### 6. Interactive States (Hover/Focus/Active)
|
|
494
|
+
- ✅ PrimeReact components handle this automatically
|
|
495
|
+
- ✅ Use semantic tokens for custom hover states
|
|
496
|
+
- ✅ Ensure keyboard navigation works
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Accessibility Requirements
|
|
501
|
+
|
|
502
|
+
Every composition MUST be accessible:
|
|
503
|
+
|
|
504
|
+
### Focus Management
|
|
505
|
+
- ✅ Ensure logical tab order
|
|
506
|
+
- ✅ Use `autoFocus` for dialogs/modals
|
|
507
|
+
- ✅ Return focus after dialog close
|
|
508
|
+
|
|
509
|
+
### ARIA Labels
|
|
510
|
+
- ✅ Provide labels for icon-only buttons
|
|
511
|
+
- ✅ Use `aria-label` when visual label missing
|
|
512
|
+
- ✅ Associate labels with form inputs
|
|
513
|
+
|
|
514
|
+
**Example:**
|
|
515
|
+
```tsx
|
|
516
|
+
<Button
|
|
517
|
+
icon="pi pi-trash"
|
|
518
|
+
aria-label="Delete user"
|
|
519
|
+
severity="danger"
|
|
520
|
+
/>
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Keyboard Navigation
|
|
524
|
+
- ✅ All interactive elements keyboard accessible
|
|
525
|
+
- ✅ Test with Tab/Enter/Escape keys
|
|
526
|
+
- ✅ DataTable supports keyboard navigation out of box
|
|
527
|
+
|
|
528
|
+
### Screen Reader Support
|
|
529
|
+
- ✅ Use semantic HTML (`<button>`, `<input>`)
|
|
530
|
+
- ✅ Provide meaningful error messages
|
|
531
|
+
- ✅ Announce dynamic changes (use Toast)
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## Block vs Component Decision
|
|
536
|
+
|
|
537
|
+
### Create a Block when:
|
|
538
|
+
✅ Pattern repeats in 2+ places
|
|
539
|
+
✅ Complex composition (3+ PrimeReact components)
|
|
540
|
+
✅ Encapsulates business logic (data fetching, state)
|
|
541
|
+
✅ Requires consistent styling across app
|
|
542
|
+
|
|
543
|
+
**Example Block Candidates:**
|
|
544
|
+
- UserCard (avatar + name + actions)
|
|
545
|
+
- SearchBar (input + dropdown filter + button)
|
|
546
|
+
- StatusBanner (message + icon + dismiss)
|
|
547
|
+
- FormSection (fieldset + legend + fields)
|
|
548
|
+
|
|
549
|
+
### Use inline composition when:
|
|
550
|
+
✅ Used only once
|
|
551
|
+
✅ Simple (1-2 components)
|
|
552
|
+
✅ View-specific layout
|
|
553
|
+
✅ No business logic
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Anti-Patterns to Avoid
|
|
558
|
+
|
|
559
|
+
### ❌ Creating Custom Components
|
|
560
|
+
|
|
561
|
+
**Bad:**
|
|
562
|
+
```tsx
|
|
563
|
+
// DON'T create custom button component
|
|
564
|
+
const CustomButton = ({ children, onClick }) => (
|
|
565
|
+
<button
|
|
566
|
+
style={{ background: '#3B82F6', color: 'white' }}
|
|
567
|
+
onClick={onClick}
|
|
568
|
+
>
|
|
569
|
+
{children}
|
|
570
|
+
</button>
|
|
571
|
+
)
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Good:**
|
|
575
|
+
```tsx
|
|
576
|
+
// USE PrimeReact Button
|
|
577
|
+
<Button label="Click me" onClick={onClick} />
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### ❌ Hardcoded Styles
|
|
581
|
+
|
|
582
|
+
**Bad:**
|
|
583
|
+
```tsx
|
|
584
|
+
<div style={{ color: '#333', margin: '16px' }}>
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Good:**
|
|
588
|
+
```tsx
|
|
589
|
+
<div
|
|
590
|
+
className="p-3" // PrimeFlex spacing
|
|
591
|
+
style={{ color: 'var(--text-neutral-default)' }} // Semantic token
|
|
592
|
+
>
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### ❌ PrimeFlex on PrimeReact Components
|
|
596
|
+
|
|
597
|
+
**Bad:**
|
|
598
|
+
```tsx
|
|
599
|
+
<Button className="bg-blue-500 text-white" label="Submit" />
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**Good:**
|
|
603
|
+
```tsx
|
|
604
|
+
<Button label="Submit" /> {/* Theme handles styling */}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### ❌ Mixing Tailwind Classes
|
|
608
|
+
|
|
609
|
+
**Bad:**
|
|
610
|
+
```tsx
|
|
611
|
+
<div className="bg-blue-500 rounded-lg shadow-md p-4">
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Good:**
|
|
615
|
+
```tsx
|
|
616
|
+
<Card>
|
|
617
|
+
<div className="flex flex-column gap-2"> {/* PrimeFlex only */}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### ❌ Inventing New Patterns
|
|
621
|
+
|
|
622
|
+
**Bad:**
|
|
623
|
+
```tsx
|
|
624
|
+
// Creating custom accordion when PrimeReact has one
|
|
625
|
+
const CustomAccordion = () => { /* ... */ }
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
**Good:**
|
|
629
|
+
```tsx
|
|
630
|
+
<Accordion>
|
|
631
|
+
<AccordionTab header="Section 1">Content</AccordionTab>
|
|
632
|
+
</Accordion>
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Example Workflows
|
|
638
|
+
|
|
639
|
+
### Workflow 1: "I need a user profile form"
|
|
640
|
+
|
|
641
|
+
**Step 1:** Identify intent
|
|
642
|
+
- Action: Edit user data
|
|
643
|
+
- Data: Name, email, avatar
|
|
644
|
+
- Components needed: Form inputs, avatar, buttons
|
|
645
|
+
|
|
646
|
+
**Step 2:** Check existing Blocks
|
|
647
|
+
- Search: `src/blocks/UserProfileBlock`
|
|
648
|
+
- Result: Not found
|
|
649
|
+
|
|
650
|
+
**Step 3:** Match to components
|
|
651
|
+
- Avatar → `Avatar`
|
|
652
|
+
- Name input → `InputText`
|
|
653
|
+
- Email input → `InputText`
|
|
654
|
+
- Actions → `Button`
|
|
655
|
+
|
|
656
|
+
**Step 4:** Build composition
|
|
657
|
+
|
|
658
|
+
```tsx
|
|
659
|
+
// UserProfileBlock.tsx
|
|
660
|
+
export function UserProfileBlock({ user, onSave, onCancel }) {
|
|
661
|
+
const [name, setName] = useState(user.name)
|
|
662
|
+
const [email, setEmail] = useState(user.email)
|
|
663
|
+
|
|
664
|
+
return (
|
|
665
|
+
<div className="flex flex-column gap-3">
|
|
666
|
+
{/* Avatar */}
|
|
667
|
+
<div className="flex justify-content-center">
|
|
668
|
+
<Avatar
|
|
669
|
+
image={user.avatar}
|
|
670
|
+
size="xlarge"
|
|
671
|
+
shape="circle"
|
|
672
|
+
/>
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
{/* Form Fields */}
|
|
676
|
+
<div className="flex flex-column gap-2">
|
|
677
|
+
<label htmlFor="name">Name</label>
|
|
678
|
+
<InputText
|
|
679
|
+
id="name"
|
|
680
|
+
value={name}
|
|
681
|
+
onChange={(e) => setName(e.target.value)}
|
|
682
|
+
/>
|
|
683
|
+
</div>
|
|
684
|
+
|
|
685
|
+
<div className="flex flex-column gap-2">
|
|
686
|
+
<label htmlFor="email">Email</label>
|
|
687
|
+
<InputText
|
|
688
|
+
id="email"
|
|
689
|
+
type="email"
|
|
690
|
+
value={email}
|
|
691
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
692
|
+
/>
|
|
693
|
+
</div>
|
|
694
|
+
|
|
695
|
+
{/* Actions */}
|
|
696
|
+
<div className="flex gap-2 justify-content-end">
|
|
697
|
+
<Button
|
|
698
|
+
label="Cancel"
|
|
699
|
+
outlined
|
|
700
|
+
onClick={onCancel}
|
|
701
|
+
/>
|
|
702
|
+
<Button
|
|
703
|
+
label="Save"
|
|
704
|
+
onClick={() => onSave({ name, email })}
|
|
705
|
+
/>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
)
|
|
709
|
+
}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
**Step 5:** State handling
|
|
713
|
+
|
|
714
|
+
```tsx
|
|
715
|
+
// In parent component/view
|
|
716
|
+
function UserProfileView() {
|
|
717
|
+
const [loading, setLoading] = useState(false)
|
|
718
|
+
const [error, setError] = useState(null)
|
|
719
|
+
const [user, setUser] = useState(null)
|
|
720
|
+
|
|
721
|
+
useEffect(() => {
|
|
722
|
+
fetchUser().then(setUser).catch(setError)
|
|
723
|
+
}, [])
|
|
724
|
+
|
|
725
|
+
if (loading) return <ProgressSpinner />
|
|
726
|
+
if (error) return <Message severity="error" text={error.message} />
|
|
727
|
+
if (!user) return <Message text="User not found" />
|
|
728
|
+
|
|
729
|
+
return (
|
|
730
|
+
<Card>
|
|
731
|
+
<UserProfileBlock
|
|
732
|
+
user={user}
|
|
733
|
+
onSave={handleSave}
|
|
734
|
+
onCancel={handleCancel}
|
|
735
|
+
/>
|
|
736
|
+
</Card>
|
|
737
|
+
)
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
### Workflow 2: "I need a product list with filters"
|
|
744
|
+
|
|
745
|
+
**Step 1:** Identify intent
|
|
746
|
+
- Action: View and filter products
|
|
747
|
+
- Data: List of products
|
|
748
|
+
- Components needed: Table/grid, filters, search
|
|
749
|
+
|
|
750
|
+
**Step 2:** Check existing Blocks
|
|
751
|
+
- Search: `src/blocks/ProductListBlock`
|
|
752
|
+
- Result: Not found
|
|
753
|
+
|
|
754
|
+
**Step 3:** Match to components
|
|
755
|
+
- Product display → `DataTable` (tabular) or `DataView` (cards)
|
|
756
|
+
- Search → `InputText` with icon
|
|
757
|
+
- Category filter → `Dropdown`
|
|
758
|
+
- Price filter → `Slider`
|
|
759
|
+
|
|
760
|
+
**Step 4:** Build composition
|
|
761
|
+
|
|
762
|
+
```tsx
|
|
763
|
+
export function ProductListBlock({ products, onProductClick }) {
|
|
764
|
+
const [search, setSearch] = useState('')
|
|
765
|
+
const [category, setCategory] = useState(null)
|
|
766
|
+
const [priceRange, setPriceRange] = useState([0, 1000])
|
|
767
|
+
|
|
768
|
+
const filteredProducts = products.filter(product => {
|
|
769
|
+
// Filter logic
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
return (
|
|
773
|
+
<div className="flex flex-column gap-3">
|
|
774
|
+
{/* Filters */}
|
|
775
|
+
<div className="grid">
|
|
776
|
+
<div className="col-12 md:col-4">
|
|
777
|
+
<InputText
|
|
778
|
+
placeholder="Search products..."
|
|
779
|
+
value={search}
|
|
780
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
781
|
+
className="w-full"
|
|
782
|
+
/>
|
|
783
|
+
</div>
|
|
784
|
+
<div className="col-12 md:col-4">
|
|
785
|
+
<Dropdown
|
|
786
|
+
placeholder="Category"
|
|
787
|
+
value={category}
|
|
788
|
+
options={categories}
|
|
789
|
+
onChange={(e) => setCategory(e.value)}
|
|
790
|
+
className="w-full"
|
|
791
|
+
/>
|
|
792
|
+
</div>
|
|
793
|
+
<div className="col-12 md:col-4">
|
|
794
|
+
<Slider
|
|
795
|
+
value={priceRange}
|
|
796
|
+
onChange={(e) => setPriceRange(e.value)}
|
|
797
|
+
range
|
|
798
|
+
min={0}
|
|
799
|
+
max={1000}
|
|
800
|
+
/>
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
|
|
804
|
+
{/* Product List */}
|
|
805
|
+
<DataView
|
|
806
|
+
value={filteredProducts}
|
|
807
|
+
itemTemplate={(product) => (
|
|
808
|
+
<Card>
|
|
809
|
+
<Image src={product.image} alt={product.name} />
|
|
810
|
+
<h3>{product.name}</h3>
|
|
811
|
+
<p style={{ color: 'var(--text-neutral-subdued)' }}>
|
|
812
|
+
{product.description}
|
|
813
|
+
</p>
|
|
814
|
+
<div className="flex justify-content-between align-items-center">
|
|
815
|
+
<span style={{
|
|
816
|
+
color: 'var(--text-brand-primary)',
|
|
817
|
+
fontSize: '1.25rem',
|
|
818
|
+
fontWeight: 600
|
|
819
|
+
}}>
|
|
820
|
+
${product.price}
|
|
821
|
+
</span>
|
|
822
|
+
<Button
|
|
823
|
+
label="View Details"
|
|
824
|
+
onClick={() => onProductClick(product)}
|
|
825
|
+
/>
|
|
826
|
+
</div>
|
|
827
|
+
</Card>
|
|
828
|
+
)}
|
|
829
|
+
paginator
|
|
830
|
+
rows={12}
|
|
831
|
+
emptyMessage="No products found"
|
|
832
|
+
/>
|
|
833
|
+
</div>
|
|
834
|
+
)
|
|
835
|
+
}
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
---
|
|
839
|
+
|
|
840
|
+
## Output Template
|
|
841
|
+
|
|
842
|
+
When responding to a UI request, use this format:
|
|
843
|
+
|
|
844
|
+
### Composition Plan
|
|
845
|
+
|
|
846
|
+
**Intent:** [What the user is trying to accomplish]
|
|
847
|
+
|
|
848
|
+
**Existing Block:** [Block name if found, or "None - creating new composition"]
|
|
849
|
+
|
|
850
|
+
**Components:**
|
|
851
|
+
- [PrimeReact Component 1] - [Purpose]
|
|
852
|
+
- [PrimeReact Component 2] - [Purpose]
|
|
853
|
+
- [PrimeFlex layout] - [Container structure]
|
|
854
|
+
|
|
855
|
+
**Composition Map:**
|
|
856
|
+
```
|
|
857
|
+
[Container name]
|
|
858
|
+
└── [PrimeFlex layout classes]
|
|
859
|
+
├── [Component 1]
|
|
860
|
+
├── [Component 2]
|
|
861
|
+
└── [Component 3]
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
**State Handling:**
|
|
865
|
+
- Loading: [Approach]
|
|
866
|
+
- Empty: [Message/template]
|
|
867
|
+
- Error: [Display method]
|
|
868
|
+
|
|
869
|
+
**Accessibility Notes:**
|
|
870
|
+
- [Focus management notes]
|
|
871
|
+
- [ARIA labels needed]
|
|
872
|
+
- [Keyboard navigation considerations]
|
|
873
|
+
|
|
874
|
+
**Code Example:**
|
|
875
|
+
```tsx
|
|
876
|
+
[Actual composition code]
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
## Quick Reference
|
|
882
|
+
|
|
883
|
+
**Before creating ANY UI component:**
|
|
884
|
+
1. ✅ Check for existing Block
|
|
885
|
+
2. ✅ Search PrimeReact catalog
|
|
886
|
+
3. ✅ Plan composition with PrimeFlex
|
|
887
|
+
4. ✅ Include all 5 states (default/loading/empty/error/disabled)
|
|
888
|
+
5. ✅ Verify accessibility
|
|
889
|
+
6. ✅ Use semantic tokens only
|
|
890
|
+
|
|
891
|
+
**Remember:**
|
|
892
|
+
- Composition > Creation
|
|
893
|
+
- PrimeReact first, always
|
|
894
|
+
- PrimeFlex for layout only
|
|
895
|
+
- Semantic tokens for design
|
|
896
|
+
- States are not optional
|
|
897
|
+
- Accessibility is required
|
|
898
|
+
|
|
899
|
+
---
|
|
900
|
+
|
|
901
|
+
**Questions to ask yourself:**
|
|
902
|
+
- Is there a PrimeReact component for this?
|
|
903
|
+
- Have we built something similar before?
|
|
904
|
+
- Am I using PrimeFlex correctly (layout only)?
|
|
905
|
+
- Have I handled all 5 states?
|
|
906
|
+
- Is this keyboard accessible?
|
|
907
|
+
- Am I using semantic tokens?
|
|
908
|
+
|
|
909
|
+
If you answer "no" to any of these, stop and reconsider your approach.
|