snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,329 @@
---
title: Mejores prácticas
icon: star
---
Este documento describe las mejores prácticas que debes seguir al trabajar en el frontend.
## Gestión de estado
React y Jotai manejan la gestión de estado en la base de código.
### Usa átomos de Jotai para almacenar el estado
Es buena práctica crear tantos átomos como necesites para almacenar tu estado.
<Warning>
Es mejor usar átomos adicionales que intentar ser demasiado concisos con la perforación de props.
</Warning>
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### No uses `useRef` para almacenar el estado
Evita usar `useRef` para almacenar el estado.
Si deseas almacenar el estado, deberías usar `useState` o átomos de Jotai con `useAtomState`.
Consulta [cómo gestionar las re-renderizaciones](#managing-re-renders) si sientes que necesitas `useRef` para evitar algunas re-renderizaciones.
## Gestión de las re-renderizaciones
Las re-renderizaciones pueden ser difíciles de gestionar en React.
Aquí hay algunas reglas a seguir para evitar re-renderizaciones innecesarias.
Ten en cuenta que siempre puedes evitar re-renderizaciones comprendiendo su causa.
### Trabaja a nivel de raíz
Ahora es fácil evitar re-renderizaciones en nuevas funciones eliminándolas a nivel de raíz.
El componente sidecar `PageChangeEffect` contiene sólo un `useEffect` que contiene toda la lógica a ejecutar en un cambio de página.
De esa manera, sabes que solo hay un lugar que puede desencadenar una re-renderización.
### Siempre piensa dos veces antes de añadir `useEffect` en tu base de código
Re-renders son a menudo causados por `useEffect` innecesario.
Debería pensar si necesita `useEffect`, o si puede mover la lógica en una función manejadora de eventos.
Generalmente te resultará fácil mover la lógica en una función `handleClick` o `handleChange`.
También puedes encontrarlas en bibliotecas como Apollo: `onCompleted`, `onError`, etc.
### Usa un componente hermano para extraer `useEffect` o lógica de obtención de datos
Si crees que necesitas añadir un `useEffect` en tu componente raíz, deberías considerar extraerlo en un componente sidecar.
Puedes aplicar lo mismo para la lógica de obtención de datos, con hooks de Apollo.
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### Usa familias de átomos y familias de selectores
Las familias de átomos y las familias de selectores son una gran manera de evitar re-renderizados.
Son útiles cuando necesitas almacenar una lista de elementos.
### No deberías usar `React.memo(MyComponent)`
Evita usar `React.memo()` porque no resuelve la causa de la re-renderización, sino que rompe la cadena de re-renderización, lo que puede llevar a un comportamiento inesperado y hacer que el código sea muy difícil de refactorizar.
### Limita el uso de `useCallback` o `useMemo`
A menudo no son necesarios y harán que el código sea más difícil de leer y mantener para una ganancia de rendimiento que es imperceptible.
## Console.logs
Las declaraciones `console.log` son valiosas durante el desarrollo, ofreciendo información en tiempo real sobre los valores de las variables y el flujo de código. Pero, dejarlas en el código de producción puede llevar a varios problemas:
1. **Rendimiento**: El registro excesivo puede afectar el rendimiento en tiempo de ejecución, especialmente en aplicaciones del lado del cliente.
2. **Seguridad**: Registrar datos sensibles puede exponer información crítica a cualquier persona que inspeccione la consola del navegador.
3. **Limpieza**: Llenar la consola con registros puede oscurecer advertencias o errores importantes que los desarrolladores o herramientas necesitan ver.
4. **Profesionalismo**: Los usuarios finales o clientes que revisen la consola y vean una miríada de declaraciones de registros podrían cuestionar la calidad y el acabado del código.
Asegúrate de eliminar todos los `console.logs` antes de enviar el código a producción.
## Nomenclatura
### Nombres de variables
Los nombres de variables deben describir con precisión el propósito o función de la variable.
#### El problema con los nombres genéricos
Los nombres genéricos en programación no son ideales porque carecen de especificidad, lo que lleva a la ambigüedad y reduce la legibilidad del código. Tales nombres no transmiten el propósito de la variable o función, lo que dificulta a los desarrolladores entender la intención del código sin una investigación más profunda. Esto puede resultar en un aumento del tiempo de depuración, una mayor susceptibilidad a errores y dificultades en el mantenimiento y colaboración. Mientras tanto, la nomenclatura descriptiva hace que el código sea autoexplicativo y más fácil de navegar, mejorando la calidad del código y la productividad del desarrollador.
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### Algunas palabras a evitar en los nombres de variables
* ficticio
### Manejadores de eventos
Los nombres de manejadores de eventos deben comenzar con `handle`, mientras que `on` es un prefijo usado para nombrar eventos en las props de los componentes.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## Props opcionales
Evita pasar el valor predeterminado para una prop opcional.
**EJEMPLO**
Toma el componente `EmailField` definido a continuación:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**Uso**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
## Componente como props
Intenta tanto como sea posible pasar componentes no instanciados como propiedades, para que los hijos puedan decidir por sí mismos qué propiedades necesitan pasar.
El ejemplo más común de esto son los componentes de icono:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
```
Para que React entienda que el componente es un componente, necesitas usar PascalCase, para luego instanciarlo con `<MyIcon>`
## Prop Drilling: Mantenlo Minimalista
El prop drilling, en el contexto de React, se refiere a la práctica de pasar variables de estado y sus setters a través de muchas capas de componentes, incluso si los componentes intermedios no los usan. Aunque a veces es necesario, el exceso de prop drilling puede llevar a:
1. **Readabilidad Reducida**: Rastrear de dónde proviene una propiedad o dónde se utiliza puede volverse complicado en una estructura de componentes muy anidada.
2. **Desafíos de Mantenimiento**: Los cambios en la estructura de propiedades de un componente podrían requerir ajustes en varios componentes, incluso si no utilizan directamente la propiedad.
3. **Reducción de la Reusabilidad del Componente**: Un componente que recibe muchas propiedades solo para pasarlas se vuelve menos general y más difícil de reutilizar en diferentes contextos.
Si sientes que estás usando en exceso el prop drilling, consulta [mejores prácticas de gestión de estado](#state-management).
## Importar
Al importar, opta por los alias designados en lugar de especificar rutas completas o relativas.
**Alias del identificador**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**Uso**
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## Validación de Esquema
[Zod](https://github.com/colinhacks/zod) es el validador de esquemas para objetos no tipados:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## Cambios Cruciales
Siempre ejecuta pruebas manuales exhaustivas antes de proceder para garantizar que las modificaciones no hayan causado interrupciones en otras partes, dado que las pruebas aún no se han integrado extensivamente.

View File

@@ -0,0 +1,110 @@
---
title: Arquitectura de Carpetas
icon: folder-tree
info: Una mirada detallada a nuestra arquitectura de carpetas
---
En esta guía, explorarás los detalles de la estructura del directorio del proyecto y cómo contribuye a la organización y mantenibilidad de Twenty.
Siguiendo esta convención de arquitectura de carpetas, es más fácil encontrar los archivos relacionados con funciones específicas y asegurar que la aplicación sea escalable y mantenible.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## Páginas
Incluye los componentes de alto nivel definidos por las rutas de la aplicación. Importan más componentes de bajo nivel de la carpeta de módulos (más detalles abajo).
## Módulos
Cada módulo representa una función o un grupo de funciones, comprendiendo sus componentes específicos, estados y lógica operativa.
Todos deberían seguir la estructura siguiente. Puedes anidar módulos dentro de módulos (denominados submódulos) y se aplicarán las mismas reglas.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### Contextos
Un contexto es una manera de pasar datos a través del árbol de componentes sin tener que pasar propiedades manualmente en cada nivel.
Ver [React Context](https://react.dev/reference/react#context-hooks) para más detalles.
### GraphQL
Incluye fragmentos, consultas y mutaciones.
Ver [GraphQL](https://graphql.org/learn/) para más detalles.
* Fragmentos
Un fragmento es una parte reutilizable de una consulta, que puedes usar en diferentes lugares. Usando fragmentos, es más fácil evitar la duplicación de código.
Ver [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) para más detalles.
* Consultas
Ver [GraphQL Queries](https://graphql.org/learn/queries/) para más detalles.
* Mutaciones
Ver [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) para más detalles.
### Hooks
Ver [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) para más detalles.
### Estados
Contiene la lógica de gestión de estado. [Jotai](https://jotai.org) maneja esto.
* Selectores: Los átomos derivados (usando `createAtomSelector`) calculan valores a partir de otros átomos y se memoizan automáticamente.
La gestión de estado incorporada de React todavía maneja el estado dentro de un componente.
### Utilidades
Debería contener solo funciones puras reutilizables. De lo contrario, crea hooks personalizados en la carpeta `hooks`.
## Interfaz de usuario
Contiene todos los componentes de interfaz de usuario reutilizables utilizados en la aplicación.
Esta carpeta puede contener subcarpetas, como `datos`, `visualización`, `retroalimentación` y `entrada` para tipos específicos de componentes. Cada componente debe ser autónomo y reutilizable, para que puedas usarlo en diferentes partes de la aplicación.
Al separar los componentes de la interfaz de usuario de otros componentes en la carpeta `modules`, es más fácil mantener un diseño consistente y realizar cambios en la interfaz de usuario sin afectar otras partes (lógica de negocio) del código base.
## Interfaz y dependencias
Puedes importar otro código de módulo desde cualquier módulo excepto desde la carpeta `ui`. Esto mantendrá su código fácil de probar.
### Interno
Cada parte (hooks, estados, ...) de un módulo puede tener una carpeta `internal`, que contiene partes que se usan solo dentro del módulo.

View File

@@ -0,0 +1,91 @@
---
title: Comandos del Frontend
icon: terminal
---
## Comandos útiles
### Iniciando la aplicación
```bash
npx nx start twenty-front
```
### Regenerar el esquema de graphql basado en el esquema API graphql
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
O
```bash
npx nx run twenty-front:graphql:generate
```
### Lint
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Traducciones
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Prueba
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## Stack Tecnológico
El proyecto tiene un stack limpio y sencillo, con un código boilerplate mínimo.
**Aplicación**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**Pruebas**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Herramientas**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## Arquitectura
### Enrutamiento
[React Router](https://reactrouter.com/) maneja el enrutamiento.
Para evitar [re-renders]innecesarios (/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) toda la lógica de enrutamiento está en un `useEffect` en `PageChangeEffect`.
### Gestión del Estado
[Jotai](https://jotai.org/) gestiona el estado.
Ver [mejores prácticas](/l/es/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) para más información sobre la gestión del estado.
## Pruebas
[Jest](https://jestjs.io/) sirve como la herramienta para pruebas unitarias mientras [Storybook](https://storybook.js.org/) es para pruebas de componentes.
Jest es principalmente para probar funciones utilitarias, y no los componentes en sí mismos.
Storybook es para probar el comportamiento de componentes aislados, así como mostrar el sistema de diseño.

View File

@@ -0,0 +1,178 @@
---
title: Atajos de teclado
---
## Introducción
Cuando necesitas escuchar una tecla de acceso rápido, normalmente usarías el evento `onKeyDown`.
En `twenty-front`, sin embargo, podrías tener conflictos entre los mismos atajos de teclado utilizados en diferentes componentes, montados al mismo tiempo.
Por ejemplo, si tienes una página que escucha la tecla Enter y un modal que escucha la tecla Enter, con un componente Select dentro de ese modal que también escucha la tecla Enter, podrías tener un conflicto cuando todos están montados al mismo tiempo.
## El gancho `useScopedHotkeys`
Para manejar este problema, tenemos un gancho personalizado que hace posible escuchar atajos de teclado sin ningún conflicto.
Lo colocas en un componente, y escuchará los atajos de teclado solo cuando el componente está montado Y cuando el **ámbito del atajo de teclado** especificado está activo.
## ¿Cómo escuchar atajos de teclado en la práctica?
Hay dos pasos involucrados en configurar la escucha de atajos de teclado:
1. Establece el [ámbito del atajo de teclado](#qué-es-un-ambito-de-atajo-de-teclado-) que escuchará los atajos de teclado
2. Usa el gancho `useScopedHotkeys` para escuchar atajos de teclado
Configurar los ámbitos de atajos de teclado es necesario incluso en páginas simples, porque otros elementos de la interfaz de usuario como el menú lateral o el menú de comandos también podrían escuchar atajos de teclado.
## Casos de uso para atajos de teclado
En general, tendrás dos casos de uso que requieren atajos de teclado:
1. En una página o un componente montado en una página
2. En un componente tipo modal que toma el enfoque debido a la acción del usuario
El segundo caso de uso puede ocurrir recursivamente: un desplegable en un modal, por ejemplo.
### Escuchando atajos de teclado en una página
Ejemplo:
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### Escuchando atajos de teclado en un componente tipo modal
Para este ejemplo utilizaremos un componente modal que escucha la tecla Escape para indicar a su padre que lo cierre.
Aquí la interacción del usuario está cambiando el ámbito.
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
Luego, en el componente modal:
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
Es importante usar este patrón cuando no está seguro de que sólo usar useEffect con montaje/desmontaje sea suficiente para evitar conflictos.
Esos conflictos pueden ser difíciles de depurar y podría ocurrir con más frecuencia que con useEffects.
## ¿Qué es un ámbito de atajo de teclado?
Un ámbito de atajo de teclado es una cadena que representa un contexto en el que los atajos de teclado están activos. Por lo general, se codifica como un enum.
Cuando cambias el ámbito del atajo de teclado, se habilitarán los atajos que están escuchando este ámbito y se deshabilitarán los atajos que escuchan otros ámbitos.
Solo puedes establecer un ámbito a la vez.
Como ejemplo, los ámbitos de atajos de teclado para cada página se definen en el enum `PageHotkeyScope`:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
Internamente, el ámbito seleccionado actualmente se almacena en un átomo de Jotai que se comparte en toda la aplicación:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
¡Pero este átomo nunca debe manejarse manualmente! Veremos cómo usarlo en la siguiente sección.
## ¿Cómo funciona internamente?
Hicimos un contenedor delgado sobre [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) que lo hace más eficiente y evita renders innecesarios.
También creamos un átomo de Jotai para manejar el estado del ámbito de atajos de teclado y hacerlo disponible en toda la aplicación.

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: Navegar por la biblioteca de componentes de interfaz de Twenty
---
Ver nuestra biblioteca completa de componentes y documentación en Storybook.
[Abrir Storybook →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: Guía de Estilo
icon: paintbrush
---
Este documento incluye las reglas a seguir al escribir código.
El objetivo aquí es tener una base de código coherente, que sea fácil de leer y de mantener.
Para esto, es mejor ser un poco más detallado que ser demasiado conciso.
Ten siempre en cuenta que la gente lee código más a menudo de lo que lo escribe, especialmente en un proyecto de código abierto, donde cualquiera puede contribuir.
Hay muchas reglas que no están definidas aquí, pero que son verificadas automáticamente por linters.
## React
### Usar componentes funcionales
Siempre usa componentes funcionales TSX.
No uses `import` por defecto con `const`, porque es más difícil de leer y más difícil de importar con el autocompletado de código.
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### "Props"
Crea el tipo de las props y llámalo `(NombreDelComponente)Props` si no hay necesidad de exportarlo.
Usa la desestructuración de props.
```tsx
// ❌ Bad, no type
export const MyComponent = (props) => <div>Hello {props.name}</div>;
// ✅ Good, type
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### Evita usar `React.FC` o `React.FunctionComponent` para definir tipos de props
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
* - With `React.FC`, the component implicitly accepts a `children` prop
* even if it's not defined in the prop type. This might not always be
* desirable, especially if the component doesn't intend to render
* children.
*/
const EmailField: React.FC<{
value: string;
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
```
#### Sin Propagación de una sola variable de Props en Elementos JSX
Evita usar la propagación de una sola variable de props en elementos JSX, como `{...props}`. Esta práctica a menudo resulta en un código que es menos legible y más difícil de mantener porque no está claro qué props está recibiendo el componente.
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
*/
const MyComponent = (props: OwnProps) => {
return <OtherComponent {...props} />;
}
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
Razonamiento:
* A simple vista, es más claro qué props se está pasando, lo que hace que sea más fácil de entender y mantener.
* Ayuda a prevenir el acoplamiento estricto entre componentes mediante sus props.
* Las herramientas de linting facilitan la identificación de props mal escritas o sin uso al listar props explícitamente.
## JavaScript
### Usar el operador de fusión nula `??`
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Usar encadenamiento opcional `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### Usar `type` en lugar de `interface`
Siempre usa `type` en lugar de `interface`, porque casi siempre se superponen y `type` es más flexible.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### Usar literales de cadena en lugar de enums
[Los literales de cadena](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) son la manera preferida para manejar valores tipo enum en TypeScript. Son más fáciles de extender con Pick y Omit, y ofrecen una mejor experiencia de desarrollo, especialmente con la autocompletación de código.
Puedes ver por qué TypeScript recomienda evitar enums [aquí](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
```tsx
// ❌ Bad, utilizes an enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
let color = Color.Red;
```
```tsx
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL y bibliotecas internas
Deberías usar enums que genera el codegen de GraphQL.
También es mejor usar un enum al usar una biblioteca interna, para que la biblioteca interna no tenga que exponer un tipo de literal de cadena que no está relacionado con la API interna.
Ejemplo:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## Estilo
### Usar StyledComponents
Estiliza los componentes con [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
Prefija los componentes estilizados con "Styled" para diferenciarlos de los componentes "reales".
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### Tematización
Utilizar el tema para la mayor parte de la estilización de los componentes es el enfoque preferido.
#### Unidades de medida
Evita usar valores `px` o `rem` directamente dentro de los componentes estilizados. Los valores necesarios suelen estar ya definidos en el tema, por lo que se recomienda usar el tema para estos fines.
#### Colores
Abstente de introducir nuevos colores; en su lugar, utiliza la paleta existente del tema. Si hay una situación en la que la paleta no se ajusta, deja un comentario para que el equipo pueda corregirlo.
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
font-weight: 400;
margin-left: 4px;
border-radius: 50px;
`;
```
```tsx
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(1)};
border-radius: ${({ theme }) => theme.border.rounded};
`;
```
## Aplicando No-Type Imports
Evita las importaciones de tipo. Para reforzar este estándar, una regla de Oxlint verifica y reporta cualquier importación de tipo. Esto ayuda a mantener la consistencia y la legibilidad en el código TypeScript.
```tsx
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
### Por qué No-Type Imports
* **Consistencia**: Al evitar las importaciones de tipo y usar un solo enfoque tanto para las importaciones de tipo como de valor, la base de código se mantiene consistente en su estilo de importación de módulos.
* **Legibilidad**: Las no-importaciones de tipo mejoran la legibilidad del código al dejar claro cuándo se están importando valores o tipos. Esto reduce la ambigüedad y hace más fácil entender el propósito de los símbolos importados.
* **Mantenibilidad**: Mejora la mantenibilidad de la base de código porque los desarrolladores pueden identificar y localizar importaciones solo de tipo al revisar o modificar el código.
### Regla de Oxlint
Una regla de Oxlint, `typescript/consistent-type-imports`, impone el estándar de no usar "type" en las importaciones. Esta regla generará errores o advertencias sobre cualquier violación de importación de tipo.
Por favor, ten en cuenta que esta regla específicamente aborda extraños casos límite donde ocurren importaciones de tipo no intencionadas. TypeScript en sí mismo desaconseja esta práctica, como se menciona en las [notas de lanzamiento de TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). En la mayoría de situaciones, no deberías necesitar usar importaciones solo de tipo.
Para asegurarte de que tu código cumpla con esta regla, asegúrate de ejecutar Oxlint como parte de tu flujo de trabajo de desarrollo.

View File

@@ -0,0 +1,61 @@
---
title: Trabajar con Figma
info: Aprende cómo puedes colaborar con Twenty en Figma
---
Figma es una herramienta de diseño de interfaces colaborativa que ayuda a superar la barrera de comunicación entre diseñadores y desarrolladores.
Esta guía explica cómo puedes colaborar con Figma.
## Acceso
1. **Accede al enlace compartido:** Puedes acceder al archivo de Figma del proyecto [aquí](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
2. **Inicia sesión:** Si aún no has iniciado sesión, Figma te pedirá que lo hagas.
Las características clave solo están disponibles para usuarios registrados, como el modo desarrollador y la capacidad de seleccionar un marco dedicado.
<Warning>
No podrás colaborar efectivamente sin una cuenta.
</Warning>
## Estructura de Figma
En la barra lateral izquierda, puedes acceder a las diferentes páginas del Figma de Twenty. Así es como están organizadas:
* **Página de componentes:** Esta es la primera página. El diseñador la utiliza para crear y organizar los elementos de diseño reutilizables que se usan en todo el archivo de diseño. Por ejemplo, botones, íconos, símbolos u otros componentes reutilizables. Sirve para mantener la consistencia a lo largo del diseño.
* **Página principal:** La segunda página es la página principal, que muestra la interfaz de usuario completa del proyecto. Puedes presionar ***Play*** para usar el prototipo completo de la aplicación.
* **Páginas de características:** Las otras páginas están típicamente dedicadas a características en progreso. Contienen el diseño de características específicas o módulos de la aplicación o sitio web. Normalmente, aún están en desarrollo.
## Consejos útiles
Con acceso de solo lectura, no puedes editar el diseño, pero puedes acceder a todas las características que serán útiles para convertir los diseños en código.
### Usa el modo Dev
El Modo Dev de Figma mejora la productividad de los desarrolladores al proporcionar una navegación fácil por el diseño, una gestión eficaz de assets, herramientas de comunicación eficientes, integraciones de caja de herramientas, fragmentos de código rápidos e información clave de capas, superando la brecha entre diseño y desarrollo. Puedes aprender más sobre el Modo Dev [aquí](https://www.figma.com/dev-mode/).
Cambia al modo "Desarrollador" en la parte derecha de la barra de herramientas para ver las especificaciones de diseño, copiar CSS y acceder a los assets.
### Usar el prototipo
Haz clic en cualquier elemento del lienzo y presiona el botón "Play" en el extremo superior derecho de la interfaz para acceder a la vista del prototipo. El modo de prototipo te permite interactuar con el diseño como si fuera el producto final. Demuestra el flujo entre pantallas y cómo los elementos de la interfaz, como botones, enlaces o menús, se comportan al interactuar con ellos.
1. **Entendiendo las transiciones y animaciones:** En el modo Prototipo, puedes ver cualquier transición o animación añadida por un diseñador entre pantallas o elementos de UI, proporcionando instrucciones visuales claras a los desarrolladores sobre la conducta y el estilo intencionados.
2. **Aclaración de implementación:** Un prototipo también puede ayudar a reducir las ambigüedades. Los desarrolladores pueden interactuar con él para obtener una mejor comprensión de la funcionalidad o apariencia de elementos particulares.
Para más detalles y orientación sobre el aprendizaje de la plataforma Figma, puedes visitar la [Documentación Oficial de Figma](https://help.figma.com/hc/en-us).
### Medir distancias
Selecciona un elemento, mantén presionada la tecla `Option` (Mac) o `Alt` (Windows), luego pasa el cursor sobre otro elemento para ver la distancia entre ellos.
### Extensión Figma para VSCode (Recomendado)
[Figma para VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
te permite navegar e inspeccionar archivos de diseño, colaborar con diseñadores, rastrear cambios y acelerar la implementación, todo sin salir de tu editor de texto.
Forma parte de nuestras extensiones recomendadas.
## Colaboración
1. **Uso de Comentarios:** Puedes utilizar la función de comentarios haciendo clic en el ícono de burbuja en la parte izquierda de la barra de herramientas.
2. **Chat de Cursor:** Una característica agradable de Figma es el Chat de Cursor. Simplemente presiona `;` en Mac y `/` en Windows para enviar un mensaje si ves a alguien más usando Figma al mismo tiempo que tú.