Create New Complementary Component
Creates a complete complementary component - from CMS schema to TypeScript types and GraphQL fragments.
Complementary components are reusable sub-components that are nested within blocks or other content types (e.g., button, video, send-email).
Reference files in this folder:
- references/file-templates.md - TypeScript templates for generated files
- references/graphql-reference.md - GraphQL fragment examples for common field types
- references/cms-reference.md - CMS field type definitions
Step 1: Guide User Through Questions (ONE AT A TIME)
Before starting: Silently scan existing complementary components to check for duplicates.
1.1 Component Name
What will the complementary component be called?
(in English, singular, e.g., 'image-gallery', 'testimonial', 'author')
Validation:
| Input | Action |
|---|---|
Czech word (autor) |
⚠️ Ask for English name |
Plural (testimonials) |
⚠️ Suggest singular, wait for confirmation |
PascalCase/camelCase (ImageGallery) |
✅ Auto-fix to kebab-case |
Underscores (image_gallery) |
✅ Auto-fix to hyphens |
Spaces (image gallery) |
✅ Auto-fix to hyphens |
Uppercase (IMAGE) |
✅ Auto-fix to lowercase |
| Special chars/diacritics | ✅ Auto-remove |
| Starts with number | ⚠️ Ask for different name |
| Already exists | ❌ Ask for different name |
⚠️ CRITICAL: Duplicate check has HIGHEST PRIORITY!
- After auto-fix, ALWAYS check if the resulting name already exists
- Example: Input
VIDEO→ auto-fix tovideo→ butvideoexists → ❌ Ask for different name - The duplicate check runs AFTER all auto-fix transformations
⚠️ IMPORTANT: When auto-fix is applied, ALWAYS inform the user what was changed:
✓ Component name: `image-gallery`
(auto-fixed: spaces → hyphens)
Examples:
| Input | Output | Message |
|---|---|---|
author |
author |
✓ Component name: author |
ImageGallery |
image-gallery |
✓ Component name: image-gallery (auto-fixed: PascalCase → kebab-case) |
image gallery |
image-gallery |
✓ Component name: image-gallery (auto-fixed: spaces → hyphens) |
image_gallery |
image-gallery |
✓ Component name: image-gallery (auto-fixed: underscores → hyphens) |
IMAGE |
image |
✓ Component name: image (auto-fixed: uppercase → lowercase) |
image! |
image |
✓ Component name: image (auto-fixed: special characters removed) |
1.2 Icon (Optional)
Which icon to use? (Optional - press Enter to skip)
Options: crown, picture, book, envelope, star, heart, quote, layout, bulletList, phone, user, file, play, question, calendar, clock, gift, globe, grid, home, link, video, bold, italic
Show: ✓ Icon: picture or ✓ Icon: (none)
Auto-generate displayName:
- Automatically convert kebab-case to PascalCase (no spaces)
- Examples:
donate-amount→DonateAmountimage-gallery→ImageGalleryauthor→Authorsend-email→SendEmail
Show: ✓ displayName: DonateAmount (auto-generated)
1.3 Define Fields
Before starting, silently scan existing complementary components in cms/src/components/complementary/ to offer them when field type is "component".
1.3.1 Ask for Field List
What fields should the component contain?
You can:
- List field names: "title, description, amount, button"
- With types: "title: string, description: text, amount: number"
- With notes: "title (required), description (optional)"
- Natural language: "title, short description, donation amount, submit button"
Examples:
- name, email, phone
- title: string, description: text, image: media
- label (required), link (optional), icon
- title, description, button (for nested component)
Parse the user's input and extract field names. Be flexible with formats:
- Comma-separated:
name, bio, avatar - With types:
name: string, bio: text - With notes:
label (required), link (optional) - Natural language: "author name, short biography, profile image"
Show extracted fields:
✓ Fields detected: title, description, amount, button
Now let's configure each field (4 total)
1.3.2 Iteratively Ask for Field Details
For each detected field, ask for missing details one field at a time.
Show clear progress with field number:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Field 1/4: title
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
What type should this field be?
1. string - Short text (e.g., names, titles)
2. text - Long text / paragraphs
3. richtext - Rich text editor with formatting
4. media - Images or files
5. boolean - True/False checkbox
6. number - Integer or decimal
7. date - Date picker
8. datetime - Date and time picker
9. relation - Relation to another content type
10. email - Email address
11. enumeration - Dropdown with predefined options
12. component - Nested component (repeatable or single)
Type:
After type is selected:
Is this field required? (Yes/No)
If type is enumeration, also ask:
What are the enum values? (comma-separated, e.g., "draft, published, archived")
If type is media, also ask:
What media types? (images, videos, files, or "all")
If type is relation, also ask:
Which content type to relate to? (e.g., "page", "article", "category")
If type is component, also ask:
Which component would you like to use?
Available complementary components:
1. complementary.button (label, page, linkExternal, openInNewTab, anchor)
2. complementary.video (uploadedVideo, externalVideo, optionalImage)
3. complementary.ecomail (apiKey, listId, ...)
4. complementary.send-email (recipient, subject, ...)
5. Other (specify manually, e.g., "block.hero-block")
Choose: 1
Is it repeatable? (Yes/No)
Show progress after each field:
✓ Field 1/4: title (string, required)
✓ Field 2/4: description (text, optional)
✓ Field 3/4: amount (integer, required)
✓ Field 4/4: button (component: complementary.button, single)
After all fields are defined, show summary:
✓ All fields configured (4/4):
- title: string (required)
- description: text (optional)
- amount: integer (required)
- button: component (complementary.button, single)
1.4 Where Will It Be Used?
Where will this component be used?
(e.g., 'in VideoBlock', 'in multiple blocks', 'in Article content type')
This is informational only - helps with documentation and context.
Show: ✓ Usage: in VideoBlock and ArticleBlock
1.5 Needs App Context?
Will this component need access to app context (IApp)?
(e.g., for page relations, locale, or global settings)
Options: Yes / No
Examples needing app:
- Button (has page relation)
- Menu items (has page relations)
Examples NOT needing app:
- Video (standalone media)
- Send-email (standalone config)
Show: ✓ Needs app context: Yes
1.6 Summary & Confirmation
📋 New Complementary Component Summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Name: donation-form
displayName: DonationForm (auto-generated)
Icon: gift
Fields: 4 fields defined
- title: string (required)
- description: text (optional)
- amount: integer (required)
- button: component (complementary.button, single)
Usage: in fundraising pages
Needs app: No
Files to create/update:
- cms/src/components/complementary/donation-form.json (new, with 4 fields)
- frontend/src/types/donationForm.ts (new)
- frontend/src/utils/strapi/getDonationFormType.ts (new)
- frontend/src/relay/app.ts (update - add fragment)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Create complementary component? (Yes/No)
Notes:
- If fields defined, show:
Fields: 4 fields definedwith full list - If no fields defined, show:
Fields: Empty (add fields in Strapi later) - Only show field list if fields were defined
Only create files after user confirms!
Step 2: Calculate Names
// Input: "image-gallery"
componentNameKebab = "image-gallery" // CMS schema name
componentNamePascal = "ImageGallery" // Type interface name + displayName
componentNameCamel = "imageGallery" // Type file name
fragmentName = "appImageGalleryFragment" // GraphQL fragment name
typeName = "ComponentComplementaryImageGallery" // GraphQL typename
transformerName = "getImageGalleryType" // Transformer function name
displayName = "ImageGallery" // Auto-generated from kebab-case (same as Pascal)
Step 3: Create Files
3.1 CMS Schema
File: cms/src/components/complementary/{componentNameKebab}.json
If fields were defined:
{
"collectionName": "components_complementary_{componentNameKebab}s",
"info": {
"displayName": "{displayName}"
// Add "icon": "{icon}" only if icon was provided
},
"options": {},
"attributes": {
"fieldName": {
"type": "string",
"required": true
},
"anotherField": {
"type": "text"
}
// ... add all defined fields here
}
}
If no fields defined (empty):
{
"collectionName": "components_complementary_{componentNameKebab}s",
"info": {
"displayName": "{displayName}"
// Add "icon": "{icon}" only if icon was provided
},
"options": {},
"attributes": {}
}
Field type mapping:
| User Input | Strapi Type | Additional Properties |
|---|---|---|
| string | "type": "string" |
- |
| text | "type": "text" |
- |
| richtext | "type": "richtext" |
- |
| media | "type": "media", "multiple": false, "allowedTypes": ["images"] |
Set allowedTypes based on user choice |
| boolean | "type": "boolean", "default": false |
- |
| number | "type": "integer" or "type": "decimal" |
Ask user which one |
| date | "type": "date" |
- |
| datetime | "type": "datetime" |
- |
"type": "email" |
- | |
| enumeration | "type": "enumeration", "enum": ["value1", "value2"] |
Use user-provided values |
| relation | "type": "relation", "relation": "oneToOne", "target": "api::page.page" |
Use user-specified target |
| component | "type": "component", "repeatable": true/false, "component": "complementary.button" |
Use user choice |
Note:
- Only include
"icon"field if user provided an icon. If they skipped it, omit the field entirely. - For required fields, add
"required": true - See references/cms-reference.md for complete field type examples
3.2 TypeScript Type Interface
File: frontend/src/types/{componentNameCamel}.ts
See references/file-templates.md for template.
3.3 Transformer Utility
File: frontend/src/utils/strapi/{transformerName}.ts
See references/file-templates.md for templates (with and without app context).
Important: Always include both functions:
{transformerName}()- Transform single item{transformerName}List()- Transform array of items (for repeatable components)
3.4 Update relay/app.ts
File: frontend/src/relay/app.ts
Add GraphQL fragment (alphabetically in the complementary fragments section):
graphql`
fragment {fragmentName} on {typeName} {
id
# TODO: Add fields after adding them in CMS
}
`;
⚠️ CRITICAL: Add fragment in the correct section (after other complementary fragments like appVideoFragment, appButtonFragment).
Step 4: Show TODO List
If fields were defined:
✅ Complementary component "{componentNameKebab}" created successfully with {X} fields!
📝 TODO (in this order):
1. [ ] Restart Strapi CMS
cd cms && npm run develop
2. [ ] Verify fields in Strapi Content-Type Builder
Components → complementary → "{displayName}"
(Fields are already created, you may adjust if needed)
3. [ ] Refresh GraphQL schema
cd frontend && npm run graphql-codegen
4. [ ] Update GraphQL fragment in relay/app.ts
fragment {fragmentName} on {typeName} {
id
fieldName1
fieldName2
...
}
(see references/graphql-reference.md for field type examples)
5. [ ] Run Relay compiler
cd frontend && npm run relay
6. [ ] Use in blocks or content types by adding component relation:
- In CMS: Add component field → Select "complementary.{componentNameKebab}"
- In block fragment: Add field with fragment spread
7. [ ] Test in CMS and frontend
If no fields defined (empty):
✅ Complementary component "{componentNameKebab}" created successfully!
📝 TODO (in this order):
1. [ ] Restart Strapi CMS
cd cms && npm run develop
2. [ ] Add fields in Strapi Content-Type Builder
Components → complementary → "{displayName}"
3. [ ] Refresh GraphQL schema
cd frontend && npm run graphql-codegen
4. [ ] Update GraphQL fragment in relay/app.ts
fragment {fragmentName} on {typeName} { ... }
(see references/graphql-reference.md for examples)
5. [ ] Run Relay compiler
cd frontend && npm run relay
6. [ ] Use in blocks or content types by adding component relation:
- In CMS: Add component field → Select "complementary.{componentNameKebab}"
- In block fragment: Add field with fragment spread
7. [ ] Test in CMS and frontend
Checklist
Questions (one at a time):
- Component name (validate: English, singular, no duplicates)
- Auto-generate displayName (kebab-case → PascalCase)
- Icon (optional)
- Define fields:
- Silently scan existing complementary components for component type suggestions
- Ask for field list (free-form text)
- Parse and extract field names
- Show "Now let's configure each field (X total)"
- For each field, ask with clear numbering "Field X/Y: fieldName":
- Field type
- Required? (Yes/No)
- Additional properties (enum values, media types, relation target, etc.)
- If type is "component": Offer existing complementary components (button, video, etc.)
- Show progress after each: "✓ Field X/Y: name (type, required)"
- After all fields: Show summary "✓ All fields configured (X/X)"
- Usage description
- Needs app context (Yes/No)
- Summary & confirmation
File creation (after confirmation):
- CMS schema (
cms/src/components/complementary/{componentNameKebab}.json)- Empty attributes if no fields defined
- Full field definitions if fields were defined
- Type interface (
frontend/src/types/{componentNameCamel}.ts) - Transformer utility with list helper (
frontend/src/utils/strapi/{transformerName}.ts)- Single item transformer:
{transformerName}() - Array transformer:
{transformerName}List()
- Single item transformer:
-
relay/app.tsupdated (add fragment) - TODO list shown to user (varies based on whether fields were defined)
Examples of Existing Complementary Components
Simple (no app context):
- video - Video with uploaded/external source and optional image
- send-email - Email configuration with recipient and subject
With app context:
- button - Button/link with page relation, external link, and label
- menu-item - Navigation item with page relation
Usage patterns:
// In a block that uses video:
import { getVideoType } from '../../../utils/strapi/getVideoType';
const video = getVideoType(blocksData.video);
// video is IVideo | null
// In a block that uses button (needs app):
import { getButtonType } from '../../../utils/strapi/getButtonType';
const button = getButtonType(blocksData.button, app);
// button is IButton | null