From shesha-developer
Use this skill when creating new designer components in Shesha package projects (e.g., enterprise, reporting, workflow). This skill guides you through implementing custom form components with proper interfaces, settings forms, component definitions, and toolbox registration following Shesha package-level patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/shesha-developer:create-custom-componentThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are an expert in the Shesha framework with deep expertise in creating custom designer components for package projects. You specialize in building reusable, well-architected form components that live in packages outside of the core `@shesha-io/reactjs` library (e.g., enterprise, reporting, workflow packages).
You are an expert in the Shesha framework with deep expertise in creating custom designer components for package projects. You specialize in building reusable, well-architected form components that live in packages outside of the core @shesha-io/reactjs library (e.g., enterprise, reporting, workflow packages).
Use this skill when:
SettingsFormMarkupFactory patternformDesignerComponents toolbox groupNote: If you are creating a component in the core
@shesha-io/reactjslibrary (using@/import aliases), use thecreate-core-componentskill instead. This skill is for package-level components that import from@shesha-io/reactjs.
| Aspect | Core Component | Package Component (this skill) |
|---|---|---|
| Imports | @/interfaces, @/providers/form/models | @shesha-io/reactjs |
| Settings form | JSON-based (settingsForm.json) | Factory-based (SettingsFormMarkupFactory) |
| Props file | interfaces.ts | model.ts |
| Settings file | settingsForm.json or settingsForm.ts | settings.ts |
| Registration | toolboxComponents.ts in core | designer-components/index.ts in package |
| Settings builder | Manual JSON markup | Fluent fbf() builder API |
Ask the user for:
IConfigurableFormComponent@ant-design/icons, e.g., "StarOutlined")components/ folder, or inline in the designer component)Find the target package and verify the directory structure. The typical package layout is:
packages/{packageName}/src/
├── components/ # Reusable UI components
│ └── {componentName}/
│ └── index.tsx
├── designer-components/ # Designer toolbox components
│ ├── index.ts # Registration file (formDesignerComponents)
│ └── {componentName}/
│ ├── index.tsx # Component definition (IToolboxComponent)
│ ├── model.ts # Props interface
│ └── settings.ts # Settings form (SettingsFormMarkupFactory)
└── index.ts # Package exports
Generate these files in src/designer-components/{componentName}/:
SettingsFormMarkupFactoryOptionally, if the component needs a separate reusable UI control:
4. src/components/{componentName}/index.tsx — The actual UI component
import { IConfigurableFormComponent } from '@shesha-io/reactjs';
/**
* Props for {ComponentDisplayName} designer component
*/
export interface I{ComponentName}Props extends IConfigurableFormComponent {
// Add custom properties here
placeholder?: string;
// ... other custom props
}
import { SettingsFormMarkupFactory } from '@shesha-io/reactjs';
import { nanoid } from 'nanoid';
export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
return fbf()
.addSectionSeparator({
id: nanoid(),
componentName: 'separator1',
parentId: 'root',
label: 'Display',
})
.addPropertyAutocomplete({
id: nanoid(),
propertyName: 'name',
componentName: 'name',
parentId: 'root',
label: 'Name',
validate: { required: true },
})
.addTextArea({
id: nanoid(),
propertyName: 'description',
componentName: 'description',
parentId: 'root',
label: 'Description',
autoSize: false,
showCount: false,
allowClear: false,
})
// Add custom property fields here
.toJson();
};
import { SettingsFormMarkupFactory } from '@shesha-io/reactjs';
import { nanoid } from 'nanoid';
export const getSettings: SettingsFormMarkupFactory = ({ fbf }) => {
const searchableTabsId = nanoid();
const commonTabId = nanoid();
const dataTabId = nanoid();
const securityTabId = nanoid();
return {
components: fbf()
.addSearchableTabs({
id: searchableTabsId,
propertyName: 'settingsTabs',
parentId: 'root',
label: 'Settings',
hideLabel: true,
labelAlign: 'right',
size: 'small',
tabs: [
{
key: '1',
title: 'Common',
id: commonTabId,
components: [
...fbf()
.addContextPropertyAutocomplete({
id: nanoid(),
propertyName: 'propertyName',
parentId: commonTabId,
label: 'Property Name',
size: 'small',
styledLabel: true,
validate: { required: true },
})
.addSettingsInputRow({
id: nanoid(),
parentId: commonTabId,
propertyName: 'editMode',
label: 'Edit Mode',
inputs: [
{
type: 'editModeSelector',
id: nanoid(),
parentId: commonTabId,
propertyName: 'editMode',
label: 'Edit Mode',
},
{
type: 'switch',
id: nanoid(),
parentId: commonTabId,
propertyName: 'hidden',
label: 'Hide',
jsSetting: true,
},
],
})
// Add more common fields here
.toJson(),
],
},
{
key: '2',
title: 'Data',
id: dataTabId,
components: [
...fbf()
// Add data-related settings here
.toJson(),
],
},
{
key: '3',
title: 'Security',
id: securityTabId,
components: [
...fbf()
.addSettingsInput({
id: nanoid(),
inputType: 'permissions',
propertyName: 'permissions',
label: 'Permissions',
size: 'small',
parentId: securityTabId,
jsSetting: true,
})
.toJson(),
],
},
],
})
.toJson(),
formSettings: {
colon: false,
layout: 'vertical',
labelCol: { span: 24 },
wrapperCol: { span: 24 },
},
};
};
import React from 'react';
import { {IconName} } from '@ant-design/icons';
import {
IToolboxComponent,
validateConfigurableComponentSettings,
} from '@shesha-io/reactjs';
import { I{ComponentName}Props } from './model';
import { getSettings } from './settings';
import {ComponentControl} from '../../components/{componentName}';
const {ComponentName}: IToolboxComponent<I{ComponentName}Props> = {
type: '{componentName}',
isInput: {isInput},
isOutput: {isOutput},
name: '{ComponentDisplayName}',
icon: <{IconName} />,
Factory: ({ model }) => {
if (model.hidden) return null;
return <{ComponentControl} {...model} />;
},
settingsFormMarkup: getSettings,
validateSettings: (model) => validateConfigurableComponentSettings(getSettings, model),
migrator: (m) => m
.add<I{ComponentName}Props>(0, (prev) => ({
...prev,
// Set default values for initial migration
})),
};
export default {ComponentName};
import React from 'react';
import { {IconName} } from '@ant-design/icons';
import {
IConfigurableFormComponent,
IToolboxComponent,
validateConfigurableComponentSettings,
} from '@shesha-io/reactjs';
import { getSettings } from './settings';
export interface I{ComponentName}Props extends IConfigurableFormComponent {
// Custom properties
}
const {ComponentName}: IToolboxComponent<I{ComponentName}Props> = {
type: '{componentName}',
isInput: {isInput},
name: '{ComponentDisplayName}',
icon: <{IconName} />,
Factory: ({ model }) => {
const { /* destructure custom props */ } = model;
if (model.hidden) return null;
return (
<div>
{/* Component implementation */}
</div>
);
},
settingsFormMarkup: getSettings,
validateSettings: (model) => validateConfigurableComponentSettings(getSettings, model),
};
export default {ComponentName};
import React, { FC } from 'react';
import { I{ComponentName}Props } from '../../designer-components/{componentName}/model';
export const {ComponentControl}: FC<I{ComponentName}Props> = (props) => {
const { /* destructure props */ } = props;
return (
<div>
{/* Component UI implementation */}
</div>
);
};
export default {ComponentControl};
Data Binding (input components):
For input components, use ConfigurableFormItem from @shesha-io/reactjs:
import { ConfigurableFormItem } from '@shesha-io/reactjs';
Factory: ({ model }) => {
if (model.hidden) return null;
return (
<ConfigurableFormItem model={model}>
{(value, onChange) => (
<YourComponent value={value} onChange={onChange} />
)}
</ConfigurableFormItem>
);
},
Container Support (if needed):
import { nanoid } from 'nanoid';
// Add to component definition:
getContainers: (model) => [
{ id: model.content?.id, components: model.content?.components ?? [] }
],
initModel: (model) => ({
...model,
content: { id: nanoid(), components: [] }
})
Open the package's src/designer-components/index.ts and add the new component:
import { IToolboxComponentBase, IToolboxComponentGroup } from '@shesha-io/reactjs';
// ... existing imports
import {ComponentName} from './{componentName}';
export const formDesignerComponents: IToolboxComponentGroup[] = [
{
name: '{GroupName}', // e.g., 'Enterprise', 'Reporting'
components: [
// ... existing components
{ComponentName},
],
visible: true,
},
];
Note: If the component has complex generics, you may need a type assertion:
{ComponentName} as unknown as IToolboxComponentBase,
Provide this checklist after generation:
src/designer-components/{componentName}/model.ts — interfaces properly typed, extends IConfigurableFormComponentsettings.ts — uses SettingsFormMarkupFactory with fbf() builderindex.tsx — exports IToolboxComponent with correct type, icon, Factorysrc/designer-components/index.ts (formDesignerComponents)src/components/{componentName}/model.hidden check present in Factory// From @shesha-io/reactjs — Component definition
import {
IToolboxComponent,
IToolboxComponentBase,
IToolboxComponentGroup,
IConfigurableFormComponent,
validateConfigurableComponentSettings,
ConfigurableFormItem,
ShaIcon,
} from '@shesha-io/reactjs';
// From @shesha-io/reactjs — Settings form
import { SettingsFormMarkupFactory } from '@shesha-io/reactjs';
// Utilities
import { nanoid } from 'nanoid';
// Icons
import { StarOutlined, EditOutlined /* etc */ } from '@ant-design/icons';
// React
import React, { FC, useState, useEffect, useMemo } from 'react';
// Ant Design UI (commonly used in components)
import { Button, Input, Select, Switch, Empty, Tag } from 'antd';
fbf()) ReferenceThe fbf() fluent builder supports these common methods:
fbf()
// Layout
.addSectionSeparator({ id, componentName, parentId, label })
.addSearchableTabs({ id, propertyName, parentId, label, hideLabel, tabs: [...] })
// Inputs
.addPropertyAutocomplete({ id, propertyName, parentId, label, validate })
.addContextPropertyAutocomplete({ id, propertyName, parentId, label, size, styledLabel, validate })
.addTextArea({ id, propertyName, parentId, label, autoSize, showCount, allowClear })
.addNumberField({ id, propertyName, parentId, label })
.addIconPicker({ id, propertyName, label, description })
// Generic settings input (supports multiple inputTypes)
.addSettingsInput({
id, propertyName, parentId, label, jsSetting,
inputType: 'dropdown' | 'codeEditor' | 'permissions' | 'queryBuilder',
dropdownOptions: [{ value, label }], // for dropdown
exposedVariables: [{ id, name, description, type }], // for codeEditor
})
// Row layout for grouping inputs
.addSettingsInputRow({
id, parentId, propertyName, label,
hidden, // optional visibility expression
inputs: [
{ type: 'editModeSelector' | 'switch' | 'autocomplete' | 'endpointsAutocomplete' | 'queryBuilder', id, propertyName, label, ... }
]
})
// Finalize
.toJson()
Ask the user if they need help with:
src/components/npx claudepluginhub shesha-io/shesha-plugins --plugin shesha-developerGenerates production-grade frontend UI components with bold design choices and working code. Accepts component descriptions, requirements, PRD files, or OpenAPI contracts.
Generates React/Vue components with TypeScript, tests, CSS modules, barrel exports, and project-pattern validation. Detects Next.js App Router and adjusts output accordingly.
Manages shadcn/ui components and projects with CLI commands, documentation, styling rules, form patterns, and composition guidance for React/Tailwind UIs.