cb7d0b83a8
## PR description This PR: - Creates a TypeScript-based extractor that discovers all exported twenty-ui components by scanning barrel files, extracting props/slots/events via ts-morph type analysis, and generating the remote DOM bindings automatically. - Adds a new ESLint rule which enforces all *Props types in twenty-ui components to be exported, which is required for the extractor to discover component prop types. Existing twenty-ui components are updated to comply with this rule. - Extends the remote DOM generation to support slots, per-component events, forwardRef wrappers, and richer property types (array, object, function) ## Edge cases to fix in another PR - Icons cannot be rendered inside buttons - IconButtons throw an error when mounted - MenuItems are not displayed correctly - MenuItemNavigate throws on click ## Video Demo https://github.com/user-attachments/assets/c2ed67cf-6a15-4896-9fec-e83fac0e862b
66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
import { type Type } from 'ts-morph';
|
|
|
|
import { type PropertySchema } from '@/front-component/types/PropertySchema';
|
|
import { isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
|
import { REACT_PROP_TO_DOM_EVENT } from '../constants/ReactPropToDomEvent';
|
|
import { mapTypeToPropertySchema } from './map-type-to-property-schema';
|
|
import { isDomEventHandler } from './is-dom-event-handler';
|
|
import { isReactElementType } from './is-react-element-type';
|
|
|
|
export type ClassifiedComponentProps = {
|
|
properties: Record<string, PropertySchema>;
|
|
events: string[];
|
|
slots: string[];
|
|
};
|
|
|
|
export const classifyComponentPropsForRemoteDomGeneration = (
|
|
propsType: Type,
|
|
): ClassifiedComponentProps => {
|
|
const properties: Record<string, PropertySchema> = {};
|
|
const events: string[] = [];
|
|
const slots: string[] = [];
|
|
const propsTypeProperties = propsType.getProperties();
|
|
|
|
for (const propertySymbol of propsTypeProperties) {
|
|
const propertyName = propertySymbol.getName();
|
|
|
|
const propertyDeclarations = propertySymbol.getDeclarations();
|
|
|
|
if (!isNonEmptyArray(propertyDeclarations)) {
|
|
continue;
|
|
}
|
|
|
|
const firstDeclaration = propertyDeclarations[0];
|
|
const propertyType = firstDeclaration.getType();
|
|
|
|
const correspondingDomEvent = REACT_PROP_TO_DOM_EVENT[propertyName];
|
|
|
|
if (isDefined(correspondingDomEvent) && isDomEventHandler(propertyType)) {
|
|
events.push(correspondingDomEvent);
|
|
|
|
continue;
|
|
}
|
|
|
|
const isOptional =
|
|
propertySymbol.isOptional() ||
|
|
propertyType.isNullable() ||
|
|
propertyType.isUndefined();
|
|
|
|
if (isReactElementType(propertyType)) {
|
|
slots.push(propertyName);
|
|
continue;
|
|
}
|
|
|
|
const classifiedType = mapTypeToPropertySchema(propertyType);
|
|
|
|
if (isDefined(classifiedType)) {
|
|
properties[propertyName] = {
|
|
type: classifiedType,
|
|
optional: isOptional,
|
|
};
|
|
}
|
|
}
|
|
|
|
return { properties, events, slots };
|
|
};
|