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: Beste Praktiken
icon: star
---
Dieses Dokument beschreibt die besten Praktiken, die Sie beim Arbeiten am Frontend beachten sollten.
## Zustandsverwaltung
React und Jotai übernehmen die Zustandsverwaltung im Code.
### Verwenden Sie Jotai-Atome, um den Zustand zu speichern
Es ist eine gute Praxis, so viele Atome zu erstellen, wie Sie benötigen, um Ihren Zustand zu speichern.
<Warning>
Es ist besser, zusätzliche Atome zu verwenden, als zu versuchen, mit Prop Drilling allzu knapp zu arbeiten.
</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>
);
}
```
### Verwenden Sie `useRef` nicht, um den Zustand zu speichern.
Vermeiden Sie die Verwendung von `useRef`, um den Zustand zu speichern.
Wenn Sie den Zustand speichern möchten, sollten Sie `useState` oder Jotai-Atome mit `useAtomState` verwenden.
Sehen Sie sich [an, wie Re-Renderings verwaltet werden können](#managing-re-renders), falls Sie das Gefühl haben, dass Sie `useRef` benötigen, um einige Re-Renderings zu verhindern.
## Re-Renderings verwalten
Re-Renderings können in React schwer zu verwalten sein.
Hier sind einige Regeln, die befolgt werden sollten, um unnötige Re-Renderings zu vermeiden.
Beachten Sie, dass Sie **immer** Re-Renderings vermeiden können, indem Sie deren Ursache verstehen.
### Arbeiten Sie auf der Root-Ebene
Das Vermeiden von Re-Renderings in neuen Funktionen wird jetzt erleichtert, indem sie auf Root-Ebene eliminiert werden.
Die `PageChangeEffect`-Sidecar-Komponente enthält nur einen `useEffect`-Hook, der die gesamte Logik für den Seitenwechsel enthält.
Auf diese Weise wissen Sie, dass es nur einen Ort gibt, der ein Re-Rendering auslösen kann.
### Denken Sie immer zweimal nach, bevor Sie `useEffect` in Ihre Codebasis aufnehmen.
Re-Renderings werden oft durch unnötige `useEffect`-Verwendungen verursacht.
Sie sollten überlegen, ob Sie `useEffect` benötigen oder ob Sie die Logik in eine Event-Handler-Funktion verschieben können.
Es ist in der Regel einfach, die Logik in eine `handleClick` oder `handleChange`-Funktion zu verschieben.
Sie können sie auch in Bibliotheken wie Apollo finden: `onCompleted`, `onError`, usw.
### Verwenden Sie eine Geschwisterkomponente, um `useEffect`- oder Datenabruf-Logik auszulagern.
Wenn Sie das Gefühl haben, Ihrer Root-Komponente einen `useEffect` hinzufügen zu müssen, sollten Sie erwägen, ihn in eine Sidecar-Komponente auszulagern.
Dasselbe können Sie auch für die Datenabruflogik mit Apollo-Hooks anwenden.
```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 />
</>
);
```
### Verwenden Sie Atom-Familienzustände und Selektoren
Atom-Familienzustände und Selektoren sind eine großartige Möglichkeit, Re-Renders zu vermeiden.
Sie sind nützlich, wenn Sie eine Liste von Elementen speichern müssen.
### Sie sollten nicht `React.memo(MyComponent)` verwenden.
Vermeiden Sie die Verwendung von `React.memo()`, da es nicht die Ursache für das Re-Rendering löst, sondern stattdessen die Re-Render-Kette unterbricht, was zu unerwartetem Verhalten führen kann und es sehr schwierig macht, den Code zu refaktorisieren.
### Beschränken Sie die Nutzung von `useCallback` oder `useMemo`.
Oft sind sie nicht erforderlich und machen den Code schwerer zu lesen und zu warten für einen Leistungsgewinn, der kaum wahrnehmbar ist.
## Console.logs
`console.log`-Anweisungen bieten während der Entwicklung wertvolle Echtzeit-Einblicke in Variablenwerte und den Codefluss. Aber wenn sie im Produktivcode belassen werden, kann dies zu mehreren Problemen führen:
1. **Leistung**: Übermäßiges Logging kann die Laufzeitleistung beeinflussen, insbesondere bei clientseitigen Anwendungen.
2. **Sicherheit**: Das Protokollieren sensibler Daten kann kritische Informationen offenlegen für jeden, der die Konsole des Browsers inspiziert.
3. **Sauberkeit**: Das Ausfüllen der Konsole mit Logs kann wichtige Warnungen oder Fehler verdecken, die Entwickler oder Tools sehen müssen.
4. **Professionalität**: Endbenutzer oder Kunden, die die Konsole überprüfen und eine Vielzahl von Protokollanweisungen sehen, könnten die Qualität und den Feinschliff des Codes in Frage stellen.
Stellen Sie sicher, dass Sie alle `console.logs` entfernen, bevor Sie den Code in die Produktion übertragen.
## Namensgebung
### Variablenbenennung
Variablennamen sollten den Zweck oder die Funktion der Variable genau beschreiben.
#### Das Problem mit generischen Namen
Generische Namen in der Programmierung sind nicht ideal, weil ihnen die Spezifität fehlt, was zu Mehrdeutigkeit und verminderter Lesbarkeit des Codes führt. Solche Namen vermitteln nicht den Zweck der Variablen oder Funktion, was es Entwicklern erschwert, die Absicht des Codes ohne tiefere Untersuchung zu verstehen. Dies kann zu erhöhten Debugging-Zeiten, höherer Fehleranfälligkeit und Schwierigkeiten bei der Wartung und Zusammenarbeit führen. In der Zwischenzeit macht eine beschreibende Namensgebung den Code selbsterklärend und einfacher zu navigieren, was die Codequalität und die Produktivität der Entwickler verbessert.
```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('');
```
#### Einige Wörter, die in Variablennamen zu vermeiden sind
* Dummy
### Ereignis-Handler
Ereignis-Handler-Namen sollten mit `handle` beginnen, während `on` als Präfix dient, um Ereignisse in Komponenten-Props zu benennen.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## Optionale Props
Vermeiden Sie es, den Standardwert für ein optionales Prop zu übergeben.
**BEISPIEL**
Betrachten Sie die unten definierte `EmailField`-Komponente:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**Verwendung**
```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" />;
```
## Komponente als Props
Versuchen Sie nach Möglichkeit, nicht instanziierte Komponenten als Props zu übergeben, damit untergeordnete Komponenten selbst entscheiden können, welche Props sie weiterreichen müssen.
Das häufigste Beispiel dafür sind Icon-Komponenten:
```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>
)
};
```
Damit React versteht, dass die Komponente eine Komponente ist, müssen Sie PascalCase verwenden, damit sie später mit `<MyIcon>` instanziiert werden kann.
## Prop Drilling: Beschränken Sie es auf das Nötigste
Prop Drilling im React-Kontext bezieht sich auf die Praxis, Zustandvariablen und deren Setter durch viele Komponentenebenen zu leiten, auch wenn Zwischenkomponenten sie nicht verwenden. Obwohl es manchmal notwendig ist, kann übermäßiges Prop Drilling zu Folgendem führen:
1. **Verminderte Lesbarkeit**: Die Nachverfolgung, woher ein Prop stammt oder wo es verwendet wird, kann in einer tief verschachtelten Komponentenstruktur verworren werden.
2. **Wartungsherausforderungen**: Änderungen in der Prop-Struktur einer Komponente können Anpassungen in mehreren Komponenten erfordern, selbst wenn sie das Prop nicht direkt verwenden.
3. **Verringerte Wiederverwendbarkeit von Komponenten**: Eine Komponente, die viele Props nur zum Weiterreichen erhält, wird weniger universell und schwieriger in unterschiedlichen Kontexten wiederzuverwenden.
Wenn Sie das Gefühl haben, dass Sie übermäßig Prop Drilling einsetzen, sehen Sie sich die [Best Practices für das Zustandsmanagement](#state-management) an.
## Importe
Beim Importieren sollten Sie die vorgesehenen Aliase anstelle der vollständigen oder relativen Pfade verwenden.
**Die Aliase**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**Verwendung**
```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';
```
## Schemavalidierung
[Zod](https://github.com/colinhacks/zod) ist der Schema-Validator für ungetypte Objekte:
```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>;
```
## Breaking Changes
Führen Sie immer gründliche manuelle Tests durch, bevor Sie fortfahren, um sicherzustellen, dass keine Modifikationen anderswo Störungen verursacht haben, da Testen noch nicht umfassend integriert ist.

View File

@@ -0,0 +1,110 @@
---
title: Ordnerarchitektur
icon: folder-tree
info: Ein detaillierter Einblick in unsere Ordnerarchitektur
---
In diesem Leitfaden erkunden Sie die Details der Projektverzeichnisstruktur und wie sie zur Organisation und Wartbarkeit von Twenty beiträgt.
Indem Sie diesem Ordnerarchitekturkonzept folgen, können Sie Dateien, die sich auf spezifische Funktionen beziehen, leichter finden und sicherstellen, dass die Anwendung skalierbar und wartbar ist.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## Seiten
Enthält die Top-Level-Komponenten, die durch die Anwendungsrouten definiert werden. Sie importieren weitere Low-Level-Komponenten aus dem Ordner modules (weitere Details unten).
## Module
Jedes Modul repräsentiert eine Funktion oder eine Gruppe von Funktionen und umfasst deren spezifische Komponenten, Zustände und Betriebslogik.
Sie sollten alle der untenstehenden Struktur folgen. Sie können Module innerhalb von Modulen (als Submodule bezeichnet) verschachteln, und die gleichen Regeln gelten.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### Kontexte
Ein Context ist eine Möglichkeit, Daten durch den Komponentenbaum weiterzugeben, ohne props auf jeder Ebene manuell weitergeben zu müssen.
Weitere Details finden Sie unter [React Context](https://react.dev/reference/react#context-hooks).
### GraphQL
Enthält Fragmente, Abfragen und Mutationen.
Weitere Details finden Sie unter [GraphQL](https://graphql.org/learn/).
* Fragmente
Ein Fragment ist ein wiederverwendbares Stück einer Abfrage, das an verschiedenen Stellen verwendet werden kann. Durch die Verwendung von Fragmenten lässt sich doppelter Code leichter vermeiden.
Weitere Details finden Sie unter [GraphQL Fragmente](https://graphql.org/learn/queries/#fragments).
* Abfragen
Weitere Details finden Sie unter [GraphQL Abfragen](https://graphql.org/learn/queries/).
* Mutationen
Weitere Details finden Sie unter [GraphQL Mutationen](https://graphql.org/learn/queries/#mutations).
### Hooks
Weitere Details finden Sie unter [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
### Zustände
Enthält die State-Management-Logik. [Jotai](https://jotai.org) übernimmt dies.
* Selektoren: Abgeleitete Atome (mit `createAtomSelector`) berechnen Werte aus anderen Atomen und werden automatisch memoisiert.
Die integrierte Zustandsverwaltung von React verwaltet weiterhin den Zustand innerhalb einer Komponente.
### Utils
Sollte nur wiederverwendbare reine Funktionen enthalten. Andernfalls erstellen Sie benutzerdefinierte Hooks im `hooks`-Ordner.
## UI
Enthält alle wiederverwendbaren UI-Komponenten, die in der Anwendung verwendet werden.
Dieser Ordner kann Unterordner wie `data`, `display`, `feedback` und `input` für bestimmte Komponententypen enthalten. Jede Komponente sollte in sich geschlossen und wiederverwendbar sein, sodass Sie sie in verschiedenen Teilen der Anwendung verwenden können.
Indem Sie die UI-Komponenten von den anderen Komponenten im `modules`-Ordner trennen, ist es einfacher, ein konsistentes Design beizubehalten und Änderungen an der UI vorzunehmen, ohne andere Bereiche (Geschäftslogik) der Codebasis zu beeinflussen.
## Schnittstelle und Abhängigkeiten
Sie können Code anderer Module aus jedem Modul importieren, mit Ausnahme des `ui`-Ordners. Dadurch bleibt der Code leicht testbar.
### Intern
Jeder Teil (Hooks, Zustände, ...) eines Moduls kann einen `internal`-Ordner haben, der Teile enthält, die nur innerhalb des Moduls verwendet werden.

View File

@@ -0,0 +1,91 @@
---
title: Frontend-Befehle
icon: terminal
---
## Nützliche Befehle
### Die App starten
```bash
npx nx start twenty-front
```
### GraphQL-Schema basierend auf API-GraphQL-Schema regenerieren
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
ODER
```bash
npx nx run twenty-front:graphql:generate
```
### Lint
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Übersetzungen
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Test
```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)
```
## Technologie-Stack
Das Projekt hat einen sauberen und einfachen Stack mit minimalem Boilerplate-Code.
**App**
* [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/)
**Tests**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Tools**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## Architektur
### Routing
[React Router](https://reactrouter.com/) übernimmt das Routing.
Um unnötige [re-renders](/l/de/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) zu vermeiden, befindet sich die gesamte Routing-Logik in einem `useEffect` in `PageChangeEffect`.
### Zustandsverwaltung
[Jotai](https://jotai.org/) übernimmt die Zustandsverwaltung.
Siehe [Best Practices](/l/de/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) für mehr Informationen zur Zustandsverwaltung.
## Tests
[Jest](https://jestjs.io/) dient als Werkzeug für Unittests, während [Storybook](https://storybook.js.org/) für Komponententests verwendet wird.
Jest ist hauptsächlich für das Testen von Dienstprogrammfunktionen und nicht für die Komponenten selbst gedacht.
Storybook dient zum Testen des Verhaltens isolierter Komponenten sowie zur Darstellung des Designsystems.

View File

@@ -0,0 +1,178 @@
---
title: Tastenkombinationen
---
## Einführung
Wenn Sie eine Tastenkombination abhören müssen, verwenden Sie normalerweise den `onKeyDown` Event-Listener.
In `twenty-front` jedoch könnten Sie Konflikte zwischen gleichen Tastenkombinationen haben, die in verschiedenen Komponenten, die gleichzeitig eingebunden sind, verwendet werden.
Zum Beispiel, wenn Sie eine Seite haben, die auf die Enter-Taste hört, und ein Modal das auf die Enter-Taste hört, und ein Select-Element innerhalb dieses Modals auf die Enter-Taste hört, könnte ein Konflikt entstehen, wenn alle gleichzeitig eingebunden sind.
## Der `useScopedHotkeys` Hook
Um dieses Problem zu lösen, verfügen wir über einen benutzerdefinierten Hook, der es ermöglicht, auf Tastenkombinationen zu hören, ohne Konflikte.
Sie platzieren ihn in einer Komponente, und er wird auf die Tastenkombinationen nur hören, wenn die Komponente eingebunden ist UND wenn der spezifizierte **Tastenkombinationsbereich** aktiv ist.
## Wie hört man in der Praxis auf Tastenkombinationen?
Es gibt zwei Schritte, um das Abhören von Tastenkombinationen einzurichten:
1. Setzen Sie den [Tastenkombinationsbereich](#what-is-a-hotkey-scope-), der auf Tastenkombinationen hören wird
2. Verwenden Sie den `useScopedHotkeys` Hook, um auf Tastenkombinationen zu hören
Das Einrichten von Tastenkombinationsbereichen ist auch bei einfachen Seiten erforderlich, da andere UI-Elemente wie das linke Menü oder das Befehlsmenü ebenfalls auf Tastenkombinationen hören könnten.
## Anwendungsfälle für Tastenkombinationen
Im Allgemeinen haben Sie zwei Anwendungsfälle, die Tastenkombinationen erfordern:
1. Auf einer Seite oder einer Komponente, die auf einer Seite eingebunden ist
2. In einer Modal-Komponente, die wegen einer Benutzeraktion den Fokus erhält
Der zweite Anwendungsfall kann rekursiv auftreten: ein Dropdown in einem Modal zum Beispiel.
### Auf Tastenkombinationen in einer Seite hören
Beispiel:
```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>;
};
```
### Auf Tastenkombinationen in einer Modal-Komponente hören
Für dieses Beispiel verwenden wir eine Modalkomponente, die auf die Escape-Taste hört, um ihrem übergeordneten Element mitzuteilen, dass sie geschlossen werden soll.
Hier ändert die Benutzerinteraktion den Bereich.
```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>;
};
```
Dann in der Modalkomponente:
```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 ist wichtig, dieses Muster zu verwenden, wenn du nicht sicher bist, dass die Verwendung eines useEffect mit mount/unmount ausreicht, um Konflikte zu vermeiden.
Diese Konflikte können schwer zu debuggen sein, und es kann öfter passieren, als nicht mit useEffects.
## Was ist ein Tastenkombinationsbereich?
Ein Tastenkombinationsbereich ist ein String, der einen Kontext darstellt, in dem die Tastenkombinationen aktiv sind. Er wird im Allgemeinen als Enum kodiert.
Wenn Sie den Tastenkombinationsbereich ändern, werden die Tastenkombinationen aktiviert, die auf diesen Bereich hören, und die Tastenkombinationen, die auf andere Bereiche hören, werden deaktiviert.
Sie können immer nur einen Bereich gleichzeitig setzen.
Als Beispiel sind die Tastenkombinationsbereiche für jede Seite im `PageHotkeyScope` Enum definiert:
```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',
}
```
Intern wird der aktuell ausgewählte Bereich in einem Jotai-Atom gespeichert, das in der gesamten Anwendung geteilt wird:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
Aber dieses Atom sollte niemals manuell bearbeitet werden! Wir werden im nächsten Abschnitt sehen, wie man es verwendet.
## Wie funktioniert es intern?
Wir haben eine dünne Schicht über [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) erstellt, die es leistungsfähiger macht und unnötige Neu-Renderings vermeidet.
Wir erstellen außerdem ein Jotai-Atom, um den Zustand des Tastenkombinationsbereichs zu verwalten und in der gesamten Anwendung verfügbar zu machen.

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: Durchsuchen Sie die UI-Komponentenbibliothek von Twenty
---
Sehen Sie sich unsere vollständige Komponentenbibliothek und Dokumentation in Storybook an.
[Storybook öffnen →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: Styleguide
icon: paintbrush
---
Dieses Dokument enthält die Regeln, die beim Schreiben von Code beachtet werden müssen.
Das Ziel ist es, eine konsistente Codebasis zu haben, die leicht lesbar und einfach zu warten ist.
Hierfür ist es besser, etwas ausführlicher zu sein als zu knapp.
Denken Sie daran, dass Code häufiger gelesen als geschrieben wird, insbesondere bei einem Open-Source-Projekt, zu dem jeder beitragen kann.
Es gibt viele Regeln, die hier nicht definiert sind, die aber automatisch durch Linters überprüft werden.
## React
### Verwenden Sie funktionale Komponenten
Verwenden Sie immer TSX-Funktionskomponenten.
Vermeiden Sie `import` mit `const`, da es schwieriger zu lesen und schwerer mit Code-Vervollständigung zu importieren ist.
```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
Erstellen Sie den Typ der Eigenschaften (props) und nennen Sie ihn `(ComponentName)Props`, wenn es nicht notwendig ist, ihn zu exportieren.
Verwenden Sie Destrukturierung der 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>;
```
#### Vermeiden Sie die Verwendung von `React.FC` oder `React.FunctionComponent`, um Prop-Typen zu definieren
```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 />
);
```
#### Kein einzelnes Variablen-Propspreading in JSX-Elementen
Vermeiden Sie das Propspreading einzelner Variablen in JSX-Elementen, wie `{...props}`. Diese Praxis führt oft zu weniger lesbarem und schwer wartbarem Code, da unklar ist, welche Props die Komponente erhält.
```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 }} />;
};
```
Rational
* Auf einen Blick ist klarer, welche Props der Code übergibt, was das Verständnis und die Wartung erleichtert.
* Es verhindert eine enge Kopplung von Komponenten über ihre Props.
* Linting-Tools erleichtern das Erkennen falsch geschriebener oder unbenutzter Props, wenn Sie Props explizit auflisten.
## JavaScript
### Verwenden Sie den Nullish-Coalescing-Operator `??`
```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';
```
### Verwenden Sie optionales Chaining `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### Verwenden Sie `type` anstelle von `interface`
Verwenden Sie immer `type` anstelle von `interface`, da sie fast immer überlappen und `type` flexibler ist.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### Verwenden Sie String-Literale anstelle von Enums
[String-Literale](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) sind die bevorzugte Methode, um enum-ähnliche Werte in TypeScript zu handhaben. Sie sind einfacher mit Pick und Omit zu erweitern und bieten eine bessere Entwicklererfahrung, vor allem mit Code-Vervollständigung.
Warum TypeScript empfiehlt, Enums zu vermeiden, sehen Sie [hier](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 und interne Bibliotheken
Sie sollten die von GraphQL Codegen generierten Enums verwenden.
Es ist auch besser, ein Enum zu verwenden, wenn eine interne Bibliothek verwendet wird, damit die interne Bibliothek keinen String-Literal-Typ freigeben muss, der nicht zur internen API gehört.
Beispiel:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## Styling
### Verwenden Sie StyledComponents
Stylen Sie die Komponenten mit [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
Prefixen Sie stilisierte Komponenten mit "Styled", um sie von "echten" Komponenten zu unterscheiden.
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### Themenbindung
Die Nutzung des Themas für den Großteil der Komponenten-Styling ist der bevorzugte Ansatz.
#### Einheiten von Messungen
Vermeiden Sie die Verwendung direkter `px`- oder `rem`-Werte innerhalb der gestylten Komponenten. Die erforderlichen Werte sind in der Regel bereits im Thema definiert, daher wird empfohlen, das Thema für diese Zwecke zu nutzen.
#### Farben
Vermeiden Sie es, neue Farben einzuführen; verwenden Sie stattdessen die vorhandene Palette aus dem Thema. Sollte es eine Situation geben, in der die Palette nicht übereinstimmt, hinterlassen Sie bitte einen Kommentar, damit das Team dies korrigieren kann.
```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};
`;
```
## Durchsetzung von No-Type Imports
Vermeiden Sie Typ-Importe. Um diesen Standard durchzusetzen, überprüft eine Oxlint-Regel alle Typ-Importe und meldet sie. Dies trägt zur Konsistenz und Lesbarkeit des TypeScript-Codes bei.
```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';
```
### Warum keine Typ-Importe?
* **Konsistenz**: Durch das Vermeiden von Typ-Importen und die Verwendung eines einzigen Ansatzes für sowohl Typ- als auch Wertimporte bleibt die Modulimportstruktur der Codebasis konsistent.
* **Lesbarkeit**: Keine Typ-Importe verbessern die Lesbarkeit des Codes, da klar wird, wann Werte oder Typen importiert werden. Dies reduziert die Zweideutigkeit und erleichtert das Verständnis des Zwecks der importierten Symbole.
* **Wartbarkeit**: Es verbessert die Wartbarkeit der Codebasis, da Entwickler Typ-Only-Imports beim Überprüfen oder Ändern von Code identifizieren und lokalisieren können.
### Oxlint-Regel
Eine Oxlint-Regel, `typescript/consistent-type-imports`, setzt den No-Type-Import-Standard durch. Diese Regel generiert Fehler oder Warnungen bei Verstößen gegen Typ-Importe.
Bitte beachten Sie, dass diese Regel speziell seltene Randfälle behandelt, in denen unbeabsichtigte Typ-Importe auftreten. TypeScript selbst lehnt diese Praxis ab, wie in den [TypeScript 3.8 Release Notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html) erwähnt. In den meisten Situationen sollten Typ-Only-Imports nicht benötigt werden.
Um sicherzustellen, dass Ihr Code mit dieser Regel übereinstimmt, achten Sie darauf, Oxlint als Teil Ihres Entwicklungsworkflows auszuführen.

View File

@@ -0,0 +1,60 @@
---
title: Mit Figma arbeiten
info: Erfahren Sie, wie Sie im Figma von Twenty zusammenarbeiten können.
---
Figma ist ein kollaboratives Interface-Design-Tool, das dazu beiträgt, die Kommunikationsbarriere zwischen Designern und Entwicklern zu überwinden.
Dieser Leitfaden erklärt, wie Sie mit Figma zusammenarbeiten können.
## Zugriff
1. **Auf den freigegebenen Link zugreifen:** Sie können auf die Figma-Datei des Projekts [hier](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty) zugreifen.
2. **Anmelden:** Wenn Sie nicht bereits angemeldet sind, werden Sie von Figma dazu aufgefordert.
Schlüsselfunktionen sind nur für angemeldete Benutzer verfügbar, wie der Entwicklermodus und die Möglichkeit, einen dedizierten Rahmen auszuwählen.
<Warning>
Ohne ein Konto können Sie nicht effektiv zusammenarbeiten.
</Warning>
## Figma-Struktur
In der linken Seitenleiste können Sie auf die verschiedenen Seiten der Figma-Datei von Twenty zugreifen. So sind sie organisiert:
* **Komponentenseite:** Dies ist die erste Seite. Der Designer verwendet sie zur Erstellung und Organisation der wiederverwendbaren Designelemente, die in der gesamten Designdatei verwendet werden. Zum Beispiel Schaltflächen, Icons, Symbole oder andere wiederverwendbare Komponenten. Sie dient dazu, die Konsistenz im gesamten Design beizubehalten.
* **Hauptseite:** Die zweite Seite ist die Hauptseite, die die vollständige Benutzeroberfläche des Projekts zeigt. Sie können ***Play*** drücken, um den vollständigen App-Prototypen zu verwenden.
* **Funktionsseiten:** Die anderen Seiten sind normalerweise den in Arbeit befindlichen Funktionen gewidmet. Sie enthalten das Design spezifischer Funktionen oder Module der Anwendung oder Website. Normalerweise sind sie noch in Arbeit.
## Nützliche Tipps
Mit Lesezugriff können Sie das Design nicht bearbeiten, aber Sie können alle Funktionen nutzen, die nützlich sind, um die Designs in Code zu konvertieren.
### Verwenden Sie den Dev-Modus
Der Dev-Modus von Figma verbessert die Produktivität von Entwicklern, indem er eine einfache Navigation im Design, effektive Ressourcenverwaltung, effiziente Kommunikationstools, Toolbox-Integrationen, schnelle Codeschnipsel und wichtige Ebeneninformationen bereitstellt, die die Lücke zwischen Design und Entwicklung schließen. Weitere Informationen über den Dev-Modus finden Sie [hier](https://www.figma.com/dev-mode/).
Wechseln Sie in der rechten Teil der Toolbar in den "Entwickler"-Modus, um Designspezifikationen anzuzeigen, CSS zu kopieren und auf Ressourcen zuzugreifen.
### Den Prototyp verwenden
Klicken Sie auf ein beliebiges Element auf der Leinwand und drücken Sie die Schaltfläche “Play” oben rechts in der Oberfläche, um die Prototyp-Ansicht zu öffnen. Der Prototypenmodus ermöglicht es Ihnen, mit dem Design zu interagieren, als wäre es das endgültige Produkt. Er zeigt den Fluss zwischen Bildschirmen und wie sich Interface-Elemente wie Schaltflächen, Links oder Menüs bei Interaktionen verhalten.
1. **Übergänge und Animationen verstehen:** Im Prototypenmodus können Sie Übergänge oder Animationen sehen, die ein Designer zwischen Bildschirmen oder UI-Elementen hinzugefügt hat, um Entwicklern klare visuelle Anweisungen zu beabsichtigtem Verhalten und Stil zu bieten.
2. **Implementierungsklarheit:** Ein Prototyp kann auch helfen, Unklarheiten zu reduzieren. Entwickler können damit interagieren, um ein besseres Verständnis der Funktionalität oder des Aussehens bestimmter Elemente zu erlangen.
Für umfassendere Details und Anleitungen zum Erlernen der Figma-Plattform können Sie die offizielle [Figma-Dokumentation](https://help.figma.com/hc/en-us) besuchen.
### Abstände messen
Wählen Sie ein Element aus, halten Sie die `Option`-Taste (Mac) oder `Alt`-Taste (Windows) gedrückt und fahren Sie dann mit der Maus über ein anderes Element, um den Abstand zwischen ihnen anzuzeigen.
### Figma-Erweiterung für VSCode (Empfohlen)
[Figma für VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension) ermöglicht es Ihnen, durch Design-Dateien zu navigieren und diese zu inspizieren, mit Designern zusammenzuarbeiten, Änderungen zu verfolgen und die Implementierung zu beschleunigen - alles, ohne Ihren Texteditor zu verlassen.
Es ist Teil unserer empfohlenen Erweiterungen.
## Zusammenarbeit
1. **Kommentare verwenden:** Sie sind eingeladen, die Kommentarfunktion zu nutzen, indem Sie auf das Blasensymbol im linken Teil der Toolbar klicken.
2. **Cursor-Chat:** Eine nette Funktion von Figma ist der Cursor-Chat. Drücken Sie einfach `;` auf Mac und `/` unter Windows, um eine Nachricht zu senden, wenn Sie sehen, dass jemand anderes Figma zur gleichen Zeit wie Sie verwendet.