[FRONT COMPONENTS] Twenty UI elements generation (#17866)

## 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
This commit is contained in:
Raphaël Bosi
2026-02-12 12:48:16 +01:00
committed by GitHub
parent c48ccbca99
commit cb7d0b83a8
133 changed files with 10556 additions and 984 deletions
@@ -2,6 +2,8 @@ import * as fs from 'fs';
import * as path from 'path';
import * as process from 'process';
import { pascalToKebab } from 'twenty-shared/utils';
import { INTROSPECTION_QUERY } from './introspection-query';
import {
type Field,
@@ -33,10 +35,6 @@ const fetchGraphQLSchema = async (): Promise<IntrospectionResponse> => {
return response.json();
};
const toKebabCase = (name: string): string => {
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
};
const unwrapType = (typeInfo: TypeRef): any => {
while (typeInfo.ofType) {
typeInfo = typeInfo.ofType;
@@ -124,7 +122,7 @@ const writeTestFile = (
): string => {
if (!content) return 'skipped';
const fileName = `${toKebabCase(queryName)}.integration-spec.ts`;
const fileName = `${pascalToKebab(queryName)}.integration-spec.ts`;
const filePath = path.join(TEST_OUTPUT_DIR, fileName);
if (fs.existsSync(filePath) && !force) {