Compare commits

...

85 Commits

Author SHA1 Message Date
Charles Bochet 3311ca1a58 Add benchmark screenshots and key findings to performance plan
Include cell render and state access benchmark result screenshots
with analysis of key findings: Jotai atoms are the dominant cost
(+312% for 10 reads/cell), derived atoms are 3x cheaper than
individual reads, component depth adds +82-109%, and styling
engines are comparable.

Made-with: Cursor
2026-02-28 13:24:07 +01:00
Charles Bochet 718a57172a Scale up benchmarks: 2000 cells, heavier atoms and state access tests
- Scale from 400 cells (50x8) to 2000 cells (200x10) to amplify
  overhead differences that were lost in noise at smaller scales
- Heavy derived atom now reads 12 sources (was 4): record data,
  selected, focused, active, dragging, pending, hovered, readOnly,
  forbidden, inputOnly, objectMetadataItems, hoverPosition
- Add H2 test: 12 individual atom reads per cell (vs 1 derived)
- Cell render test 13 now reads 10 atoms (was 3), test 16 reads 10
- Add objectMetadataItems global atom (30 objects x 20 fields each)
  to simulate the real large metadata reads
- Clarify UI: "Avg/cell" label, explicit total cell count in header

Made-with: Cursor
2026-02-28 13:18:00 +01:00
Charles Bochet cb128c4158 fix: separate Linaria/Emotion to prevent wyw from mangling Emotion styled
The wyw-in-js (Linaria) plugin was processing ALL files in __perf__/,
including those with Emotion styled components. wyw incorrectly
transforms emotionStyled.div tagged templates, producing null at
runtime. withTheme(null) then crashes accessing .name.

Fix: extract Linaria styled components to perf-linaria-cells.tsx (only
file in wyw include), keep Emotion styled in the main audit file (not
wyw-processed). Also wrap RecordTablePerfPage in ThemeProvider +
ThemeContextProvider for Emotion theme interpolation tests.

Made-with: Cursor
2026-02-28 13:08:50 +01:00
Charles Bochet 0288545d7b Upgrade perf benchmarks: Linaria support, 17+12 tests, visual results
- Add **/__perf__/**/*.tsx to wyw-in-js vite plugin include list,
  enabling Linaria styled components in benchmark files
- Rewrite cell render benchmark with 17 incremental tests:
  inline styles vs Linaria (static/dynamic) vs Emotion (static/dynamic/
  theme/withTheme), context reads (3/5), useState (1/2), event handlers,
  Jotai atoms, 4-level styled nesting, full simulation, 14 wrappers
- Rewrite state access benchmark with 12 tests:
  props vs context (1/2), pre-resolved ctx, Jotai selectors (1/3/5),
  derived atoms, ctx+atoms, unstable refs, memo, nested providers
- Each test runs 5 iterations with min/avg/max stats and bar chart
- Auto-run-all mode with sequential execution and progress indicator
- Render grid offscreen to isolate measurement from layout shift

Made-with: Cursor
2026-02-28 13:00:43 +01:00
Charles Bochet 996947a3cb Fix 2026-02-28 12:48:05 +01:00
Charles Bochet 05917e23a9 fix: move perf route outside AppRouterProviders to bypass auth
The /__perf__/table route was nested inside AppRouterProviders which
wraps all children in AuthProvider and MetadataGater, causing
unauthenticated redirects. Now it's a sibling route outside the
auth-wrapped tree.

Made-with: Cursor
2026-02-28 12:29:57 +01:00
Charles Bochet 3f0cfe66d1 Move perf route to BlankLayout to avoid auth redirect
Made-with: Cursor
2026-02-28 12:28:03 +01:00
Charles Bochet 30dc70a943 Fix POC benchmarks: replace Linaria styled with inline styles
Linaria requires build-time processing and crashes at runtime.
Use plain inline styles for benchmark components since they are
throwaway perf tooling.

Made-with: Cursor
2026-02-28 12:26:22 +01:00
Charles Bochet 7221b45ff7 Delegate onMouseMove from per-cell to table level
Replace 400 individual cell onMouseMove handlers with a single delegated
handler on RecordTableContent. Uses DOM id parsing to resolve cell position,
with ref-based deduplication to skip redundant updates when mouse stays
in the same cell. Also removes useRecordTableBodyContextOrThrow from
RecordTableCellBaseContainer.

Made-with: Cursor
2026-02-28 12:22:25 +01:00
Charles Bochet 49d05f3ca7 Optimize RecordTable cell display path
- Replace FieldFocusContextProvider (useState per cell) with static
  providers: unfocused for display, focused for hover portal. Eliminates
  400 useState instances on a typical table render.
- Hoist useObjectMetadataItems from per-cell RecordTableCellFieldContextGeneric
  to table-level RecordTableContext, eliminating per-cell atom reads.
- Remove useFieldFocus and onMouseLeave handler from RecordTableCellBaseContainer,
  as focus state is now managed by static context providers.

Made-with: Cursor
2026-02-28 12:20:28 +01:00
Charles Bochet 0e44e07fcf Add RecordTable performance audit tooling and improvement plan
Adds profiling instrumentation, POC benchmarks, and a detailed
performance plan for iteratively simplifying the RecordTable cell
render path (currently 31 components deep, 14 per cell).

Made-with: Cursor
2026-02-28 12:13:29 +01:00
Abdul Rahman 9fe2a07c55 Navbar customization v2 (#18026)
Adds color support for navigation menu items.

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-28 11:48:28 +01:00
github-actions[bot] 63f17eec2c i18n - translations (#18300)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-27 23:43:42 +01:00
Abdul Rahman e806d36099 Navbar with AI chats (#18161)
## Summary
Add Home/Chat tabs and a dedicated threads list in the navigation
drawer.

## Changes
- **Navbar tabs:** Tabs in the drawer to switch between Home and Chat
(with “New chat” button). Shown on desktop when expanded and on mobile
below the workspace selector.
- **Navbar threads list:** New `NavigationDrawerAIChatThreadsList` for
the Chat tab with date groups (Today / Yesterday / Older), thread rows
as `NavigationDrawerItem` (IconComment, title, timestamp). Shared
`useAIChatThreadClick` hook used by navbar and command menu; navbar
passes `resetNavigationStack: true`.
- **NavigationDrawerItem:** New `alwaysShowRightOptions` prop so the
timestamp is always visible (no hover-only).

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-27 23:37:14 +01:00
github-actions[bot] 9f5a8735c9 i18n - docs translations (#18280)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-27 23:12:39 +01:00
github-actions[bot] c0e6aa1c0b i18n - translations (#18295)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-27 23:12:15 +01:00
Charles Bochet 9342b16aad Fix more tests 2 (#18293)
## Summary
- Migrate more hand-written test mocks to auto-generated data from a
real Twenty instance
- Add generators for views, billing plans, API keys; extend record
generator for workspace members, favorites, connected accounts, calendar
events
- Remove 9 hand-written mock files replaced by generated equivalents
- Update 16 test/story files to use generated data
- Fix WorkflowEditActionEmailBase story assertion to match configured
recipient email

## Test plan
- [x] Lint, typecheck, unit tests pass
- [ ] Storybook tests pass in CI
2026-02-27 23:11:56 +01:00
Baptiste Devessier cfad24da48 Fix empty user id clickhouse (#18238)
- Fixes:
- Make Workspace User select work; previously, it didn't work as we were
not fetching the workspace users correctly
  - Send Object Events with valid record id and object id

## Audit logs demo


https://github.com/user-attachments/assets/92437037-d253-4810-a138-7c709550755d
2026-02-27 16:58:44 +01:00
Charles Bochet 86fbf69e95 Fix more tests (#18287)
Improve mock in front tests
2026-02-27 14:06:18 +01:00
github-actions[bot] 9667b3f369 i18n - translations (#18291)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-27 14:05:24 +01:00
Abdullah. 76c7639eb3 fix: upgrade storybook to latest to resolve dependabot alert (#18285)
Resolves [Dependabot Alert
509](https://github.com/twentyhq/twenty/security/dependabot/509).

Upgraded storybook and related packages to latest, also fixed a failing
test to match what the DOM really contains.
2026-02-27 10:58:54 +01:00
Abdullah. 4ed09a3feb Upgrade blocknote dependencies from 0.31.1 to 0.47.0. (#18207)
This PR pgrades all BlockNote packages (@blocknote/core,
@blocknote/react, @blocknote/mantine, @blocknote/server-util,
@blocknote/xl-docx-exporter, @blocknote/xl-pdf-exporter) to 0.47.0 and
adapts the codebase to the new API.

### Changes
- Dependency upgrades: Bumped all BlockNote packages to 0.47.0, added
required Mantine v8 peer dependencies, removed unnecessary prosemirror
resolutions
- Formatting toolbar: Replaced the manual reimplementation of
FormattingToolbarController (which handled visibility, positioning,
portal rendering, text-alignment-based placement, and a
dangerouslySetInnerHTML transition trick) with BlockNote's built-in
FormattingToolbarController. The toolbar buttons themselves are
unchanged.
- Side menu: Replaced manual drag handle menu positioning and rendering
(DashboardBlockDragHandleMenu, DashboardBlockColorPicker, and their
floating configs) with BlockNote's built-in SideMenuController,
DragHandleButton, and DragHandleMenu components. Deleted 4 files that
became dead code.
- Extension API migration: Replaced deprecated editor.suggestionMenus
and editor.formattingToolbar APIs with the new extension system
(SuggestionMenu, useExtensionState, editor.getExtension())
- Slash menu fixes: Filtered out BlockNote's new default "File" item
(added in 0.47) to avoid duplicates with our custom one; added icon
mappings for new block types (Toggle List, Divider, Toggle Headings,
Headings 4-6)
- Server-side: Switched @blocknote/server-util to dynamic import() to
handle ESM-only transitive dependencies in CJS context
2026-02-27 09:16:49 +01:00
Thomas Trompette def5ea5764 Fix ai agent node prompt and variables (#18275)
- prompt stored on workflow lvl so input variables can be resolved and
it can evolves with versions
- make ai agent node output available as variables
2026-02-26 17:17:48 +01:00
github-actions[bot] 81698ff32c i18n - docs translations (#18274)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-26 16:52:43 +01:00
github-actions[bot] c0c51f2ef5 i18n - translations (#18278)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-26 16:48:08 +01:00
Weiko 3bbaff801a Add Twenty app settings custom app (#18273)
## Context
This PR adds the ability to define a front-component as a custom tab for
the application settings, allowing app creators to inject some
logic/rendering the their app settings.


## Example
```typescript
// packages/twenty-apps/my-test-app/src/front-components/settings-custom-tab.tsx
import { defineFrontComponent } from 'twenty-sdk';

export const SETTINGS_CUSTOM_TAB_UNIVERSAL_IDENTIFIER =
  'a42a88a8-21ce-4d22-bc44-d5146da64726';

export const SettingsCustomTab = () => {
  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <h2>My Test App Settings</h2>
      <p>This is a custom settings tab provided by My Test App.</p>
      <p>Application creators can customize this component freely.</p>
    </div>
  );
};

export default defineFrontComponent({
  universalIdentifier: SETTINGS_CUSTOM_TAB_UNIVERSAL_IDENTIFIER,
  name: 'settings-custom-tab',
  description: 'Custom settings tab for the application',
  component: SettingsCustomTab,
});
```

```typescript
// packages/twenty-apps/my-test-app/src/application-config.ts
export default defineApplication({
  universalIdentifier: '52870ce6-e584-4bd4-bc1a-6c63c508982e',
  displayName: 'My test app',
  description: '',
  defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
  settingsCustomTabFrontComponentUniversalIdentifier:
    SETTINGS_CUSTOM_TAB_UNIVERSAL_IDENTIFIER,
});
```
<img width="786" height="757" alt="Screenshot 2026-02-26 at 14 54 09"
src="https://github.com/user-attachments/assets/fcba70db-35da-48f1-bd65-359e894a691d"
/>
2026-02-26 16:26:49 +01:00
Abdullah. 2d2fc06265 fix: basic-ftp related dependabot alert (#18269)
Resolves [Dependabot Alert
507](https://github.com/twentyhq/twenty/security/dependabot/507).

Fixes critical SLA breach in 6 days.
2026-02-26 14:50:49 +01:00
Charles Bochet 8a7a19f312 Improve test tooling (#18259)
## Summary

Unifies test mocking tooling across Jest and Storybook, replaces
handcrafted mock data with auto-generated server-fetched data, and
restructures the mock data generation script for maintainability.

### Mock data generation

- Split `generate-mock-data.ts` into three focused modules under
`scripts/mock-data/`:
  - `utils.ts` — shared authentication, GraphQL client, and file writer
  - `generate-metadata.ts` — fetches object metadata from `/metadata`
- `generate-record-data.ts` — fetches record data from `/graphql` using
metadata-driven dynamic queries
- The orchestrator (`generate-mock-data.ts`) authenticates once and
passes the token to both generators
- Company records are now fetched from the actual server (limited to 10
records) instead of being handcrafted
- Generated files are organized under `generated/metadata/objects/` and
`generated/data/companies/`

### Unified test utilities

- Consolidated Jest and MSW mocking into shared utilities that compose
production code (`prefillRecord`, `getRecordNodeFromRecord`,
`getRecordConnectionFromRecords`) with mock metadata
- Renamed `generateEmptyJestRecordNode` → `generateMockRecordNode` and
moved to `testing/utils/`
- Extracted `generateMockRecordConnection` into its own file
- Removed `sanitizeInputForPrefill` workaround (no longer needed with
correctly shaped generated data)
2026-02-26 14:31:54 +01:00
Etienne 2f0103faa5 OAuth Client - Add OAuth propagator (#18266)
For OAuth Server without wildcard redirect URL

https://www.benanderson.co.uk/2023/07/28/dynamic-redirect-uris-oauth/
2026-02-26 13:44:54 +01:00
dependabot[bot] 08bfbfda45 Bump @emotion/styled from 11.13.0 to 11.14.1 (#18253)
Bumps [@emotion/styled](https://github.com/emotion-js/emotion) from
11.13.0 to 11.14.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/emotion-js/emotion/releases"><code>@​emotion/styled</code>'s
releases</a>.</em></p>
<blockquote>
<h2><code>@​emotion/styled</code><a
href="https://github.com/11"><code>@​11</code></a>.14.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/emotion-js/emotion/pull/3334">#3334</a>
<a
href="https://github.com/emotion-js/emotion/commit/0facbe47bd9099ae4ed22dc201822d910ac3dec5"><code>0facbe4</code></a>
Thanks <a
href="https://github.com/ZachRiegel"><code>@​ZachRiegel</code></a>! -
Renamed default-exported variable in <code>@emotion/styled</code> to aid
inferred import names in auto-import completions in IDEs</li>
</ul>
<h2><code>@​emotion/styled</code><a
href="https://github.com/11"><code>@​11</code></a>.14.0</h2>
<h3>Minor Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/emotion-js/emotion/pull/3284">#3284</a>
<a
href="https://github.com/emotion-js/emotion/commit/a19d019bd418ebc3b9cba0e58f58b36ac2862a42"><code>a19d019</code></a>
Thanks <a
href="https://github.com/Andarist"><code>@​Andarist</code></a>! - Source
code has been migrated to TypeScript. From now on type declarations will
be emitted based on that, instead of being hand-written.</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/emotion-js/emotion/commit/e1bf17ee87ec51da1412eb5291460ea95a39d27a"><code>e1bf17e</code></a>]:
<ul>
<li><code>@​emotion/use-insertion-effect-with-fallbacks</code><a
href="https://github.com/1"><code>@​1</code></a>.2.0</li>
</ul>
</li>
</ul>
<h2><code>@​emotion/styled</code><a
href="https://github.com/11"><code>@​11</code></a>.13.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/emotion-js/emotion/pull/3270">#3270</a>
<a
href="https://github.com/emotion-js/emotion/commit/77d930dc708015ff6fd34a1084bb343b02d732fa"><code>77d930d</code></a>
Thanks <a
href="https://github.com/emmatown"><code>@​emmatown</code></a>! - Fix
inconsistent hashes using development vs production
bundles/<code>exports</code> conditions when using
<code>@emotion/babel-plugin</code> with <code>sourceMap: true</code>
(the default). This is particularly visible when using Emotion with the
Next.js Pages router where the <code>development</code> condition is
used when bundling code but not when importing external code with
Node.js.</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/emotion-js/emotion/commit/77d930dc708015ff6fd34a1084bb343b02d732fa"><code>77d930d</code></a>]:</p>
<ul>
<li><code>@​emotion/serialize</code><a
href="https://github.com/1"><code>@​1</code></a>.3.3</li>
<li><code>@​emotion/utils</code><a
href="https://github.com/1"><code>@​1</code></a>.4.2</li>
<li><code>@​emotion/babel-plugin</code><a
href="https://github.com/11"><code>@​11</code></a>.13.5</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/emotion-js/emotion/commit/49229553967b6050c92d9602eb577bdc48167e91"><code>4922955</code></a>
Version Packages (<a
href="https://redirect.github.com/emotion-js/emotion/issues/3335">#3335</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/0facbe47bd9099ae4ed22dc201822d910ac3dec5"><code>0facbe4</code></a>
Renamed default-exported variable in <code>@emotion/styled</code> to aid
inferred import...</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/cce67ec6b2fc94261028b4f4778aae8c3d6c5fd6"><code>cce67ec</code></a>
Bump parcel (<a
href="https://redirect.github.com/emotion-js/emotion/issues/3258">#3258</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/3c19ce5997f73960679e546af47801205631dfde"><code>3c19ce5</code></a>
Version Packages (<a
href="https://redirect.github.com/emotion-js/emotion/issues/3280">#3280</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/a19d019bd418ebc3b9cba0e58f58b36ac2862a42"><code>a19d019</code></a>
Convert <code>@emotion/styled</code>'s source code to TypeScript (<a
href="https://redirect.github.com/emotion-js/emotion/issues/3284">#3284</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/5974e33fcb5e7aee177408684ac6fe8b38b3e353"><code>5974e33</code></a>
Fix JSX namespace <a
href="https://github.com/ts-ignores"><code>@​ts-ignores</code></a> (<a
href="https://redirect.github.com/emotion-js/emotion/issues/3282">#3282</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/fc4d7bd744c205f55513dcd4e4e5134198c219de"><code>fc4d7bd</code></a>
Convert <code>@emotion/react</code>'s source code to TypeScript (<a
href="https://redirect.github.com/emotion-js/emotion/issues/3281">#3281</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/8dc1a6dd19d2dc9ce435ef0aff85ccf5647f5d2e"><code>8dc1a6d</code></a>
Convert <code>@emotion/cache</code>'s source code to TypeScript (<a
href="https://redirect.github.com/emotion-js/emotion/issues/3277">#3277</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/282b61d2ad4e39ea65af88351a894a903c2d42c4"><code>282b61d</code></a>
Convert <code>@emotion/css-prettifier</code>'s source code to TypeScript
(<a
href="https://redirect.github.com/emotion-js/emotion/issues/3278">#3278</a>)</li>
<li><a
href="https://github.com/emotion-js/emotion/commit/e1bf17ee87ec51da1412eb5291460ea95a39d27a"><code>e1bf17e</code></a>
Convert <code>@emotion/use-insertion-effect-with-fallbacks</code>'s
source code to TypeS...</li>
<li>Additional commits viewable in <a
href="https://github.com/emotion-js/emotion/compare/@emotion/styled@11.13.0...@emotion/styled@11.14.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@emotion/styled&package-manager=npm_and_yarn&previous-version=11.13.0&new-version=11.14.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 12:59:59 +01:00
Raphaël Bosi c025a0c2b8 Add events and properties to video, audio and iFrame (#18257)
- Add media-specific events
- Extend `iFrame` with missing properties
- Enrich `SerializedEventData` with media-related target fields so
serialized events carry the media element state.
- Refactor the `remote-elements` code generator to support per-element
custom events
2026-02-26 12:14:51 +01:00
Paul Rastoin 5e19361494 Allow uuidv5 for universal identifier (#18265)
Twenty apps are using v5
2026-02-26 12:13:38 +01:00
dependabot[bot] f11d76d6cf Bump @babel/preset-react from 7.26.3 to 7.28.5 (#18254)
Bumps
[@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react)
from 7.26.3 to 7.28.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/babel/babel/releases"><code>@​babel/preset-react</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v7.28.5 (2025-10-23)</h2>
<p>Thank you <a
href="https://github.com/CO0Ki3"><code>@​CO0Ki3</code></a>, <a
href="https://github.com/Olexandr88"><code>@​Olexandr88</code></a>, and
<a href="https://github.com/youthfulhps"><code>@​youthfulhps</code></a>
for your first PRs!</p>
<h4>👓 Spec Compliance</h4>
<ul>
<li><code>babel-parser</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17446">#17446</a>
Allow <code>Runtime Errors for Function Call Assignment Targets</code>
(<a
href="https://github.com/liuxingbaoyu"><code>@​liuxingbaoyu</code></a>)</li>
</ul>
</li>
<li><code>babel-helper-validator-identifier</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17501">#17501</a>
fix: update identifier to unicode 17 (<a
href="https://github.com/fisker"><code>@​fisker</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>babel-plugin-proposal-destructuring-private</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17534">#17534</a>
Allow mixing private destructuring and rest (<a
href="https://github.com/CO0Ki3"><code>@​CO0Ki3</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17521">#17521</a>
Improve <code>@babel/parser</code> error typing (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
<li><a
href="https://redirect.github.com/babel/babel/pull/17491">#17491</a>
fix: improve ts-only declaration parsing (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-proposal-discard-binding</code>,
<code>babel-plugin-transform-destructuring</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17519">#17519</a>
fix: <code>rest</code> correctly returns plain array (<a
href="https://github.com/liuxingbaoyu"><code>@​liuxingbaoyu</code></a>)</li>
</ul>
</li>
<li><code>babel-helper-create-class-features-plugin</code>,
<code>babel-helper-member-expression-to-functions</code>,
<code>babel-plugin-transform-block-scoping</code>,
<code>babel-plugin-transform-optional-chaining</code>,
<code>babel-traverse</code>, <code>babel-types</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17503">#17503</a> Fix
<code>JSXIdentifier</code> handling in
<code>isReferencedIdentifier</code> (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-traverse</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17504">#17504</a>
fix: ensure scope.push register in anonymous fn (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏠 Internal</h4>
<ul>
<li><code>babel-types</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17494">#17494</a>
Type checking babel-types scripts (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
</ul>
<h4>🏃‍♀️ Performance</h4>
<ul>
<li><code>babel-core</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17490">#17490</a>
Faster finding of locations in <code>buildCodeFrameError</code> (<a
href="https://github.com/liuxingbaoyu"><code>@​liuxingbaoyu</code></a>)</li>
</ul>
</li>
</ul>
<h4>Committers: 8</h4>
<ul>
<li>Babel Bot (<a
href="https://github.com/babel-bot"><code>@​babel-bot</code></a>)</li>
<li>Byeongho Yoo (<a
href="https://github.com/youthfulhps"><code>@​youthfulhps</code></a>)</li>
<li>Huáng Jùnliàng (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
<li>Hyeon Dokko (<a
href="https://github.com/CO0Ki3"><code>@​CO0Ki3</code></a>)</li>
<li>Nicolò Ribaudo (<a
href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
<li><a
href="https://github.com/Olexandr88"><code>@​Olexandr88</code></a></li>
<li><a
href="https://github.com/liuxingbaoyu"><code>@​liuxingbaoyu</code></a></li>
<li>fisker Cheung (<a
href="https://github.com/fisker"><code>@​fisker</code></a>)</li>
</ul>
<h2>v7.28.4 (2025-09-05)</h2>
<p>Thanks <a
href="https://github.com/gwillen"><code>@​gwillen</code></a> and <a
href="https://github.com/mrginglymus"><code>@​mrginglymus</code></a> for
your first PRs!</p>
<h4>🏠 Internal</h4>
<ul>
<li><code>babel-core</code>,
<code>babel-helper-check-duplicate-nodes</code>,
<code>babel-traverse</code>, <code>babel-types</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17493">#17493</a>
Update Jest to v30.1.1 (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-regenerator</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17455">#17455</a>
chore: Clean up <code>transform-regenerator</code> (<a
href="https://github.com/liuxingbaoyu"><code>@​liuxingbaoyu</code></a>)</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/babel/babel/blob/main/CHANGELOG.md"><code>@​babel/preset-react</code>'s
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<blockquote>
<p><strong>Tags:</strong></p>
<ul>
<li>💥 [Breaking Change]</li>
<li>👓 [Spec Compliance]</li>
<li>🚀 [New Feature]</li>
<li>🐛 [Bug Fix]</li>
<li>📝 [Documentation]</li>
<li>🏠 [Internal]</li>
<li>💅 [Polish]</li>
</ul>
</blockquote>
<p><em>Note: Gaps between patch versions are faulty, broken or test
releases.</em></p>
<p>This file contains the changelog starting from v8.0.0-alpha.0.</p>
<ul>
<li>See <a
href="https://github.com/babel/babel/blob/main/.github/CHANGELOG-v7.15.0-v7.28.5.md">CHANGELOG
- v7.15.0 to v7.28.5</a> for v7.15.0 to v7.28.5 changes (the last common
release between the v8 and v7 release lines was v7.28.5).</li>
<li>See <a
href="https://github.com/babel/babel/blob/main/.github/CHANGELOG-v7.0.0-v7.14.9.md">CHANGELOG
- v7.0.0 to v7.14.9</a> for v7.0.0 to v7.14.9 changes.</li>
<li>See <a
href="https://github.com/babel/babel/blob/main/.github/CHANGELOG-v7-prereleases.md">CHANGELOG
- v7 prereleases</a> for v7.0.0-alpha.1 to v7.0.0-rc.4 changes.</li>
<li>See <a
href="https://github.com/babel/babel/blob/main/.github/CHANGELOG-v4.md">CHANGELOG
- v4</a>, <a
href="https://github.com/babel/babel/blob/main/.github/CHANGELOG-v5.md">CHANGELOG
- v5</a>, and <a
href="https://github.com/babel/babel/blob/main/.github/CHANGELOG-v6.md">CHANGELOG
- v6</a> for v4.x-v6.x changes.</li>
<li>See <a
href="https://github.com/babel/babel/blob/main/.github/CHANGELOG-6to5.md">CHANGELOG
- 6to5</a> for the pre-4.0.0 version changelog.</li>
<li>See <a
href="https://github.com/babel/babel/blob/main/packages/babel-parser/CHANGELOG.md">Babylon's
CHANGELOG</a> for the Babylon pre-7.0.0-beta.29 version changelog.</li>
<li>See <a
href="https://github.com/babel/babel-eslint/releases"><code>babel-eslint</code>'s
releases</a> for the changelog before <code>@babel/eslint-parser</code>
7.8.0.</li>
<li>See <a
href="https://github.com/babel/eslint-plugin-babel/releases"><code>eslint-plugin-babel</code>'s
releases</a> for the changelog before <code>@babel/eslint-plugin</code>
7.8.0.</li>
</ul>
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<h2>v8.0.0-rc.2 (2026-02-15)</h2>
<h4>💥 Breaking Change</h4>
<ul>
<li>Other
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17766">#17766</a>
Remove unused code for old ESLint versions (<a
href="https://github.com/liuxingbaoyu"><code>@​liuxingbaoyu</code></a>)</li>
</ul>
</li>
<li><code>babel-code-frame</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17772">#17772</a>
Remove deprecated default export from <code>@babel/code-frame</code> (<a
href="https://github.com/fisker"><code>@​fisker</code></a>)</li>
</ul>
</li>
</ul>
<h4>🐛 Bug Fix</h4>
<ul>
<li><code>babel-helpers</code>,
<code>babel-plugin-transform-async-generator-functions</code>,
<code>babel-runtime-corejs3</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17797">#17797</a>
fix: Properly handle <code>await</code> in <code>finally</code> (<a
href="https://github.com/liuxingbaoyu"><code>@​liuxingbaoyu</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17796">#17796</a>
Support ESLint 10 (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-preset-env</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17787">#17787</a>
Fix: preset-env include/exclude should accept bugfix plugins (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-generator</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17781">#17781</a>
fix: preserve trailing comma in optional call args (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
<li><a
href="https://redirect.github.com/babel/babel/pull/17774">#17774</a> Fix
<code>undefined</code> indentation when exactly 64 indents (<a
href="https://github.com/YoussefHenna"><code>@​YoussefHenna</code></a>)</li>
</ul>
</li>
<li><code>babel-standalone</code>
<ul>
<li><a
href="https://redirect.github.com/babel/babel/pull/17770">#17770</a>
fix: ensure <code>targets.esmodules</code> is validated (<a
href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
</ul>
<h4>💅 Polish</h4>
<ul>
<li><code>babel-core</code></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/babel/babel/commit/61647ae2397c82c3c71f077b5ab109106a5cac0f"><code>61647ae</code></a>
v7.28.5</li>
<li><a
href="https://github.com/babel/babel/commit/42cb285b59fc99a8102d69bef6223b75617e9f46"><code>42cb285</code></a>
Improve <code>@babel/core</code> types (<a
href="https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react/issues/17404">#17404</a>)</li>
<li><a
href="https://github.com/babel/babel/commit/eebd3a06021c13d335b5b0bd79734df3abbea678"><code>eebd3a0</code></a>
v7.27.1</li>
<li><a
href="https://github.com/babel/babel/commit/fdc0fb59e119ee0b38bced63867a344a5b4bc2f3"><code>fdc0fb5</code></a>
[Babel 8] Bump nodejs requirements to <code>^20.19.0 || &gt;=
22.12.0</code> (<a
href="https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react/issues/17204">#17204</a>)</li>
<li><a
href="https://github.com/babel/babel/commit/cd24cc07ef6558b7f6510f9177f6393c91b0549f"><code>cd24cc0</code></a>
chore: Update TS 5.7 (<a
href="https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react/issues/17053">#17053</a>)</li>
<li>See full diff in <a
href="https://github.com/babel/babel/commits/v7.28.5/packages/babel-preset-react">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by [GitHub Actions](<a
href="https://www.npmjs.com/~GitHub">https://www.npmjs.com/~GitHub</a>
Actions), a new releaser for <code>@​babel/preset-react</code> since
your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@babel/preset-react&package-manager=npm_and_yarn&previous-version=7.26.3&new-version=7.28.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 11:57:26 +01:00
Paul Rastoin 3aedce9af7 Fix remaining non v4 uuid universal identifier (#18263) 2026-02-26 11:24:01 +01:00
dependabot[bot] 8e8ecfb8a3 Bump @sentry/react from 10.27.0 to 10.40.0 (#18252)
Bumps [@sentry/react](https://github.com/getsentry/sentry-javascript)
from 10.27.0 to 10.40.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/getsentry/sentry-javascript/releases"><code>@​sentry/react</code>'s
releases</a>.</em></p>
<blockquote>
<h2>10.40.0</h2>
<h3>Important Changes</h3>
<ul>
<li>
<p><strong>feat(tanstackstart-react): Add global sentry exception
middlewares (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19330">#19330</a>)</strong></p>
<p>The <code>sentryGlobalRequestMiddleware</code> and
<code>sentryGlobalFunctionMiddleware</code> global middlewares capture
unhandled exceptions thrown in TanStack Start API routes and server
functions. Add them as the first entries in the
<code>requestMiddleware</code> and <code>functionMiddleware</code>
arrays of <code>createStart()</code>:</p>
<pre lang="ts"><code>import { createStart } from
'@tanstack/react-start/server';
import { sentryGlobalRequestMiddleware, sentryGlobalFunctionMiddleware }
from '@sentry/tanstackstart-react';
<p>export default createStart({
requestMiddleware: [sentryGlobalRequestMiddleware, myRequestMiddleware],
functionMiddleware: [sentryGlobalFunctionMiddleware,
myFunctionMiddleware],
});
</code></pre></p>
</li>
<li>
<p><strong>feat(tanstackstart-react)!: Export Vite plugin from
<code>@sentry/tanstackstart-react/vite</code> subpath (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19182">#19182</a>)</strong></p>
<p>The <code>sentryTanstackStart</code> Vite plugin is now exported from
a dedicated subpath. Update your import:</p>
<pre lang="diff"><code>- import { sentryTanstackStart } from
'@sentry/tanstackstart-react';
+ import { sentryTanstackStart } from
'@sentry/tanstackstart-react/vite';
</code></pre>
</li>
<li>
<p><strong>fix(node-core): Reduce bundle size by removing apm-js-collab
and requiring pino &gt;= 9.10 (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/18631">#18631</a>)</strong></p>
<p>In order to keep receiving pino logs, you need to update your pino
version to &gt;= 9.10, the reason for the support bump is to reduce the
bundle size of the node-core SDK in frameworks that cannot tree-shake
the apm-js-collab dependency.</p>
</li>
<li>
<p><strong>fix(browser): Ensure user id is consistently added to
sessions (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19341">#19341</a>)</strong></p>
<p>Previously, the SDK inconsistently set the user id on sessions,
meaning sessions were often lacking proper coupling to the user set for
example via <code>Sentry.setUser()</code>.
Additionally, the SDK incorrectly skipped starting a new session for the
first soft navigation after the pageload.
This patch fixes these issues. As a result, metrics around sessions,
like &quot;Crash Free Sessions&quot; or &quot;Crash Free Users&quot;
might change.
This could also trigger alerts, depending on your set thresholds and
conditions.
We apologize for any inconvenience caused!</p>
<p>While we're at it, if you're using Sentry in a Single Page App or
meta framework, you might want to give the new <code>'page'</code>
session lifecycle a try!
This new mode no longer creates a session per soft navigation but
continues the initial session until the next hard page refresh.
Check out the <a
href="https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/integrations/browsersession/">docs</a>
to learn more!</p>
</li>
<li>
<p><strong>ref!(gatsby): Drop Gatsby v2 support (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19467">#19467</a>)</strong></p>
<p>We drop support for Gatsby v2 (which still relies on webpack 4) for a
critical security update in <a
href="https://github.com/getsentry/sentry-javascript-bundler-plugins/releases/tag/5.0.0">https://github.com/getsentry/sentry-javascript-bundler-plugins/releases/tag/5.0.0</a></p>
</li>
</ul>
<h3>Other Changes</h3>
<ul>
<li>feat(astro): Add support for Astro on CF Workers (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19265">#19265</a>)</li>
<li>feat(cloudflare): Instrument async KV API (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19404">#19404</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md"><code>@​sentry/react</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>10.40.0</h2>
<h3>Important Changes</h3>
<ul>
<li>
<p><strong>feat(tanstackstart-react): Add global sentry exception
middlewares (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19330">#19330</a>)</strong></p>
<p>The <code>sentryGlobalRequestMiddleware</code> and
<code>sentryGlobalFunctionMiddleware</code> global middlewares capture
unhandled exceptions thrown in TanStack Start API routes and server
functions. Add them as the first entries in the
<code>requestMiddleware</code> and <code>functionMiddleware</code>
arrays of <code>createStart()</code>:</p>
<pre lang="ts"><code>import { createStart } from
'@tanstack/react-start/server';
import { sentryGlobalRequestMiddleware, sentryGlobalFunctionMiddleware }
from '@sentry/tanstackstart-react/server';
<p>export default createStart({
requestMiddleware: [sentryGlobalRequestMiddleware, myRequestMiddleware],
functionMiddleware: [sentryGlobalFunctionMiddleware,
myFunctionMiddleware],
});
</code></pre></p>
</li>
<li>
<p><strong>feat(tanstackstart-react)!: Export Vite plugin from
<code>@sentry/tanstackstart-react/vite</code> subpath (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19182">#19182</a>)</strong></p>
<p>The <code>sentryTanstackStart</code> Vite plugin is now exported from
a dedicated subpath. Update your import:</p>
<pre lang="diff"><code>- import { sentryTanstackStart } from
'@sentry/tanstackstart-react';
+ import { sentryTanstackStart } from
'@sentry/tanstackstart-react/vite';
</code></pre>
</li>
<li>
<p><strong>fix(node-core): Reduce bundle size by removing apm-js-collab
and requiring pino &gt;= 9.10 (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/18631">#18631</a>)</strong></p>
<p>In order to keep receiving pino logs, you need to update your pino
version to &gt;= 9.10, the reason for the support bump is to reduce the
bundle size of the node-core SDK in frameworks that cannot tree-shake
the apm-js-collab dependency.</p>
</li>
<li>
<p><strong>fix(browser): Ensure user id is consistently added to
sessions (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19341">#19341</a>)</strong></p>
<p>Previously, the SDK inconsistently set the user id on sessions,
meaning sessions were often lacking proper coupling to the user set for
example via <code>Sentry.setUser()</code>.
Additionally, the SDK incorrectly skipped starting a new session for the
first soft navigation after the pageload.
This patch fixes these issues. As a result, metrics around sessions,
like &quot;Crash Free Sessions&quot; or &quot;Crash Free Users&quot;
might change.
This could also trigger alerts, depending on your set thresholds and
conditions.
We apologize for any inconvenience caused!</p>
<p>While we're at it, if you're using Sentry in a Single Page App or
meta framework, you might want to give the new <code>'page'</code>
session lifecycle a try!
This new mode no longer creates a session per soft navigation but
continues the initial session until the next hard page refresh.
Check out the <a
href="https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/integrations/browsersession/">docs</a>
to learn more!</p>
</li>
<li>
<p><strong>ref!(gatsby): Drop Gatsby v2 support (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19467">#19467</a>)</strong></p>
<p>We drop support for Gatsby v2 (which still relies on webpack 4) for a
critical security update in <a
href="https://github.com/getsentry/sentry-javascript-bundler-plugins/releases/tag/5.0.0">https://github.com/getsentry/sentry-javascript-bundler-plugins/releases/tag/5.0.0</a></p>
</li>
</ul>
<h3>Other Changes</h3>
<ul>
<li>feat(astro): Add support for Astro on CF Workers (<a
href="https://redirect.github.com/getsentry/sentry-javascript/pull/19265">#19265</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/663fd5e7e3c1808d4a636f001d768845f167668e"><code>663fd5e</code></a>
Increase bundler-tests timeout to 30s</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/8033ea380f0526cc863c6d50347fd5747ae5df32"><code>8033ea3</code></a>
release: 10.40.0</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/eb3c4d2489a77753377f7e3a320f18cd853ebf6a"><code>eb3c4d2</code></a>
Merge pull request <a
href="https://redirect.github.com/getsentry/sentry-javascript/issues/19488">#19488</a>
from getsentry/prepare-release/10.40.0</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/9a10630c6b7524d053b96cfaafa14751b0611f33"><code>9a10630</code></a>
meta(changelog): Update changelog for 10.40.0</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/39d1ef77849223f7742999c808f7f23da0c42adf"><code>39d1ef7</code></a>
fix(deps): Bump to latest version of each minimatch major (<a
href="https://redirect.github.com/getsentry/sentry-javascript/issues/19486">#19486</a>)</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/e8ed6d262f7f43cef8b04265794db83ab013f95c"><code>e8ed6d2</code></a>
test(nextjs): Deactivate canary test for cf-workers (<a
href="https://redirect.github.com/getsentry/sentry-javascript/issues/19483">#19483</a>)</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/6eb320eb3e01985720238c8f08e3ac114502059b"><code>6eb320e</code></a>
chore(deps): Bump Sentry CLI to latest v2 (<a
href="https://redirect.github.com/getsentry/sentry-javascript/issues/19477">#19477</a>)</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/8fc81d2cd4048fb41b49e773d4829d9fb799f16c"><code>8fc81d2</code></a>
fix: Bump bundler plugins to v5 (<a
href="https://redirect.github.com/getsentry/sentry-javascript/issues/19468">#19468</a>)</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/365f7fab4e33d69363d4eb6d99e5f87e48672fba"><code>365f7fa</code></a>
chore(ci): Adapt max turns of triage issue agent (<a
href="https://redirect.github.com/getsentry/sentry-javascript/issues/19473">#19473</a>)</li>
<li><a
href="https://github.com/getsentry/sentry-javascript/commit/11e5412d42f6126e5415d67d1418ffdb17f5caa6"><code>11e5412</code></a>
feat(tanstackstart-react)!: Export Vite plugin from
<code>@​sentry/tanstackstart-rea</code>...</li>
<li>Additional commits viewable in <a
href="https://github.com/getsentry/sentry-javascript/compare/10.27.0...10.40.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sentry/react&package-manager=npm_and_yarn&previous-version=10.27.0&new-version=10.40.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 11:16:24 +01:00
martmull 120096346a Add define post isntall logic function (#18248)
As title
2026-02-26 11:09:21 +01:00
Paul Rastoin fde8168a85 Centralized universal identifier validation on create (#18258) 2026-02-26 10:48:24 +01:00
Charles Bochet 2674589b44 Remove any recoil reference from project (#18250)
## Remove all Recoil references and replace with Jotai

### Summary

- Removed every occurrence of Recoil from the entire codebase, replacing
with Jotai equivalents where applicable
- Updated `README.md` tech stack: `Recoil` → `Jotai`
- Rewrote documentation code examples to use
`createAtomState`/`useAtomState` instead of `atom`/`useRecoilState`, and
removed `RecoilRoot` wrappers
- Cleaned up source code comment and Cursor rules that referenced Recoil
- Applied changes across all 13 locale translations (ar, cs, de, es, fr,
it, ja, ko, pt, ro, ru, tr, zh)
2026-02-26 10:28:40 +01:00
Charles Bochet f325509d96 Fix Jotai state leak in frontend tests causing unbounded memory growth (#18249)
## Summary

The global `jotaiStore` singleton was shared across all tests without
reset, leaking state between test suites and causing unbounded heap
growth within each Jest worker.

- `getJestMetadataAndApolloMocksWrapper` now creates a **fresh Jotai
store per wrapper** (equivalent of RecoilRoot isolation), so each test
gets clean state
- Added `workerIdleMemoryLimit: '512MB'` to recycle workers that still
accumulate memory
- Set `maxWorkers: 3` for CI

## Performance

Measured on 48 test suites (command-menu + views + object-record hooks),
single worker:

| Metric | Before | After |
|---|---|---|
| Peak heap | **1519 MB** | **~530 MB** (-65%) |
| Final heap | **1519 MB** | **437 MB** (-71%) |
| Growth pattern | Monotonic (never freed) | Bounded sawtooth (recycled
at 512MB) |
2026-02-26 01:20:25 +01:00
nitin a9649b06d5 followup 18044 (#18213)
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-26 00:55:57 +01:00
Charles Bochet 0b4bf97f35 Fix twenty-sdk typecheck race condition (#18246)
## Fix SDK first-run build failure when generated API client is missing

### Problem

When running `twenty app:dev` for the first time, the build pipeline
crashes because:

1. The esbuild front-component watcher tries to resolve
`twenty-sdk/generated`, but the generated API client doesn't exist yet —
it's only created later in the pipeline after syncing with the server.
2. The typecheck plugin (`tsc --noEmit`) runs as a blocking esbuild
`onStart` hook, so even with stub files, type errors on the empty client
classes (e.g. `Property 'query' does not exist on type 'CoreApiClient'`)
cause the build to fail before the real client can be generated.

This creates a chicken-and-egg problem: the watchers need the generated
client to build, but the client is generated after the watchers produce
their output.

### Solution

**1. Stub generated client on startup**

Added `ensureGeneratedClientStub` to `ClientService` that writes minimal
placeholder files (`CoreApiClient`, `MetadataApiClient`) into
`node_modules/twenty-sdk/generated/` if the directory doesn't already
exist. This is called in `DevModeOrchestrator.start()` before any
watchers are created, so the `twenty-sdk/generated` import always
resolves.

**2. Skip typecheck on first sync round**

Made the esbuild typecheck plugin accept a `shouldSkipTypecheck`
callback. The orchestrator starts with `skipTypecheck = true` and passes
`() => this.skipTypecheck` through the watcher chain. After the real API
client is generated, the flag is flipped to `false`, so subsequent
rebuilds enforce full type checking with the real generated types.
2026-02-25 23:47:08 +01:00
github-actions[bot] 4ea8245387 i18n - docs translations (#18245)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-25 23:42:24 +01:00
Lucas Bordeau 0fa8063054 Handle ObjectMetadataItem, FieldMetadataItem and NavigationMenuItem with SSE events (#18235)
This PR allows to handle ObjectMetadataItem, FieldMetadataItem and
NavigationMenuItem SSE events.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-25 23:18:00 +01:00
Abdullah. 546114d07f fix: next-mdx-remote related dependabot alerts (#18244)
Resolves [Dependabot Alert
485](https://github.com/twentyhq/twenty/security/dependabot/485) and
[Dependabot Alert
486](https://github.com/twentyhq/twenty/security/dependabot/486).

Bumped up `next-mdx-remote` to v6.0.0 and `remark-gfm` to v4.0.1 for
compatibility - no breaking changes for our use-case.
2026-02-25 23:12:23 +01:00
github-actions[bot] 92824c6533 i18n - translations (#18243)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-25 22:34:27 +01:00
Charles Bochet bc4ae35bc4 Rework atom naming (#18240)
## Summary

- **Enhanced the `matching-state-variable` ESLint rule** to enforce
consistent naming for `ComponentState`, `FamilyState`, and
`ComponentFamilyState` hooks — previously it only covered `useAtomState`
and `useAtomStateValue`
- **The rule now checks 12 hooks** across three categories: value hooks
(`useAtomComponentStateValue`, `useAtomFamilyStateValue`, etc.), state
hooks (`useAtomComponentState`, `useAtomComponentFamilyState`), and
setter hooks (`useSetAtomState`, `useSetAtomComponentState`,
`useSetAtomFamilyState`, `useSetAtomComponentFamilyState`)
- **Fixed all 225 resulting lint violations** across 151 files, renaming
variables to match their state atom names (e.g. `currentViewId` →
`contextStoreCurrentViewId`, `selectedRecord` → `recordStore`). Cases
where the same state is accessed with different family keys/instance IDs
are suppressed with `eslint-disable-next-line`.

## Naming convention

| Hook | State argument | Valid | Invalid |
|------|---------------|-------|---------|
| `useAtomStateValue` | `fooState` | `const foo = ...` | `const bar =
...` |
| `useAtomComponentStateValue` | `fooComponentState` | `const foo = ...`
| `const bar = ...` |
| `useAtomFamilyStateValue` | `fooFamilyState` | `const foo = ...` |
`const bar = ...` |
| `useAtomComponentFamilyStateValue` | `fooComponentFamilyState` |
`const foo = ...` | `const bar = ...` |
| `useAtomState` | `fooState` | `const [foo, setFoo] = ...` | `const
[bar, setBar] = ...` |
| `useAtomComponentState` | `fooComponentState` | `const [foo, setFoo] =
...` | `const [bar, setBar] = ...` |
| `useAtomComponentFamilyState` | `fooComponentFamilyState` | `const
[foo, setFoo] = ...` | `const [bar, setBar] = ...` |
| `useSetAtomState` | `fooState` | `const setFoo = ...` | `const setBar
= ...` |
| `useSetAtomComponentState` | `fooComponentState` | `const setFoo =
...` | `const setBar = ...` |
| `useSetAtomFamilyState` | `fooFamilyState` | `const setFoo = ...` |
`const setBar = ...` |
| `useSetAtomComponentFamilyState` | `fooComponentFamilyState` | `const
setFoo = ...` | `const setBar = ...` |
2026-02-25 22:28:25 +01:00
Etienne 1b805a36f3 Fix page layout widget creation with front component (#18242) 2026-02-25 22:07:51 +01:00
Baptiste Devessier f7ea1d9500 Update doc for front components (#18196) 2026-02-25 22:04:37 +01:00
Abdullah. cbb0f212cf fix: fast-xml-parser related dependabot alerts (#18241)
Resolves [Dependabot Alert
459](https://github.com/twentyhq/twenty/security/dependabot/459) and
[Dependabot Alert
483](https://github.com/twentyhq/twenty/security/dependabot/483).

Upgraded AWS SDKs pulling in fast-xml-parser as transitive dependency.
2026-02-25 22:01:34 +01:00
Weiko 0af980a783 introduce metadata api client to twenty sdk (#18233)
Logic function: hello-world.ts
```typescript
import { CoreApiClient } from 'twenty-sdk/generated/core';
import { MetadataApiClient } from 'twenty-sdk/generated/metadata';

const handler = async () => {
  const coreClient = new CoreApiClient();
  const metadataClient = new MetadataApiClient();

  // Query the core /graphql endpoint — fetch some people
  const coreResult = await coreClient.query({
    people: {
      edges: {
        node: {
          id: true,
          name: {
            firstName: true,
            lastName: true,
          },
        },
      },
    },
  });

  // Query the metadata /metadata endpoint — fetch current workspace
  const metadataResult = await metadataClient.query({
    currentWorkspace: {
      id: true,
      displayName: true,
    },
  });

  return {
    coreResponse: coreResult,
    metadataResponse: metadataResult,
  };
};
```
With route trigger should now produce:
<img width="582" height="238" alt="Screenshot 2026-02-25 at 17 14 29"
src="https://github.com/user-attachments/assets/8c597113-7552-4d32-845a-352083d84ac7"
/>

```json
{
  "coreResponse": {
    "people": {
      "edges": [
        {
          "node": {
            "id": "20202020-b000-4485-94de-70c2a98daef2",
            "name": {
              "firstName": "Jeffery",
              "lastName": "Griffin"
            }
          }
        },
        {
          "node": {
            "id": "20202020-b003-415a-9051-133248495f7f",
            "name": {
              "firstName": "Terry",
              "lastName": "Melendez"
            }
          }
        },
        {
          "node": {
            "id": "20202020-b00e-4bc1-87c8-00aeb49c10f8",
            "name": {
              "firstName": "Lee",
              "lastName": "Jones"
            }
          }
        },
        {
          "node": {
            "id": "20202020-b012-44c1-9fdc-90f110962d07",
            "name": {
              "firstName": "Sarah",
              "lastName": "Hernandez"
            }
          }
        },
      ]
    }
  },
...
  "metadataResponse": {
    "currentWorkspace": {
      "id": "20202020-1c25-4d02-bf25-6aeccf7ea419",
      "displayName": "Apple"
    }
  }
}
2026-02-25 21:42:05 +01:00
Charles Bochet e01b641a05 Introduce npx nx mock:generate twenty-front (#18237)
## Add codegen script for frontend test mock data

### Summary

- Adds a new `npx nx mock:generate twenty-front` and
`generate-mock-data.ts` script that fetches object metadata from a
running server's `/metadata` endpoint, authenticates with default seeds,
and writes the result to a generated TypeScript file
(`src/testing/mock-data/generated/mock-metadata-query-result.ts`). This
replaces hand-maintained mock metadata with server-sourced data,
ensuring tests always reflect the real schema.
- Updates all frontend tests to be compatible with the newly generated
metadata, fixing hard-coded GraphQL queries, Zod validation schemas,
snapshot expectations, and Apollo mock mismatches.

### What changed

**New files**
- `scripts/generate-mock-data.ts` — codegen script that authenticates
against the server, queries `/metadata` for all object metadata (with
explicit `__typename` at every level), and writes a typed `.ts` file.
- `project.json` — added `mock:generate` Nx target (`dotenv npx tsx
scripts/generate-mock-data.ts`).

**Schema validation updates**
- `objectMetadataItemSchema.ts` — added `universalIdentifier`, made
`duplicateCriteria` nullable.
- `fieldMetadataItemSchema.ts` — added `universalIdentifier`, `morphId`,
`morphRelations`, restructured relation schema.
- `indexMetadataItemSchema.ts` — added optional `isCustom` field.
2026-02-25 21:40:05 +01:00
nitin 50be97422d Fix dropdown scroll by restoring scroll container nesting order (#18239) 2026-02-25 19:55:08 +01:00
Weiko 4ae8381ffc logicFunction sourceHandlerPath and builtHandlerPath manifest updates are not saved (#18230)
and delete legacy files
2026-02-25 19:24:24 +01:00
Paul Rastoin 856859f9f3 Fix front comp build/run order to be processed before dependent entities (#18232) 2026-02-25 18:52:59 +01:00
Paul Rastoin 98d67e0f6b Fix jwt auth guard for application (#18234) 2026-02-25 18:01:55 +01:00
Charles Bochet 1fa55bfd02 Fix bugs tied to jotai migration (#18227)
Fixing a few bugs:
- CommandMenu not reactive
- Filtering on index view infinite loop
2026-02-25 17:11:12 +01:00
github-actions[bot] 001c2097a3 i18n - docs translations (#18231)
Created by Github action

<!-- mintlify-editor-comments:start -->
Mintlify
---
0 threads from 0 users in Mintlify

- No unresolved comments
<!-- mintlify-editor-comments:end -->

<!-- mintlify-comment-->

<a
href="https://dashboard.mintlify.com/twenty/twenty/editor/i18n-docs?source=pr_comment"
target="_blank" rel="noopener noreferrer"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://d3gk2c5xim1je2.cloudfront.net/assets/open-mintlify-editor-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://d3gk2c5xim1je2.cloudfront.net/assets/open-mintlify-editor-light.svg"><img
src="https://d3gk2c5xim1je2.cloudfront.net/assets/open-mintlify-editor-light.svg"
alt="Open in Mintlify Editor"></picture></a>

<!-- /mintlify-comment -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-25 16:50:28 +01:00
Abdullah. 9107f5bbc7 feat: upgrade ai package to version six and the corresponding @ai-sdk/* packages to compatible versions (#18172)
Used the migration guide to carry out this upgrade:
https://ai-sdk.dev/docs/migration-guides/migration-guide-6-0

I have not been able to test locally due to credits.

<img width="220" height="450" alt="image"
src="https://github.com/user-attachments/assets/050b34b9-3239-4010-8c47-b43d44571994"
/>

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-02-25 16:49:26 +01:00
Raphaël Bosi 435e21d23f [FRONT COMPONENTS] Serialize relation between widget and front component (#18228)
This allows us to define page layout widgets of type front component in
an app with the front component universal identifier.
2026-02-25 15:01:50 +01:00
martmull 448241540d Improve create-twenty-app command (#18226)
as title
2026-02-25 14:10:47 +01:00
neo773 e7f958c9cf Fix Google API Availability and a edge case (#18150)
First run shows a real customer account, and the second run is my
account with all the permissions.

<img width="1490" height="386" alt="SCR-20260221-bmfd"
src="https://github.com/user-attachments/assets/1e2bd269-67ab-450d-9bf3-3c4872b90773"
/>


Simulated edge case if user has access to neither

<img width="343" height="162" alt="SCR-20260221-bloo"
src="https://github.com/user-attachments/assets/a697302b-e8b0-432e-bd15-f689da06dc0e"
/>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-02-25 14:02:19 +01:00
Paul Rastoin 98e73791a4 Resolve involved application ids from API metadata (#18221)
# Introduction
Resolving each create|updated|deleted entities related entities
application through their universal foreingKey ( silently failing if not
found leaving the validator handling that )
In order to compute all the required application in the dependency flat
entity maps

## Tested
Creating a field on a custom local twenty-apps installed on the
workspace + view field

## Out of scope
- updated snapshot
- update graphql generated front
2026-02-25 13:58:38 +01:00
nitin f3857c41b8 fix dashboard duplicate (#18193)
closes https://github.com/twentyhq/twenty/issues/18192
what was broken - wrong client


before - 


https://github.com/user-attachments/assets/1da60e07-622c-4884-b5bb-9eb7fc954782




after  - 


https://github.com/user-attachments/assets/fcf299f0-2515-410b-ad60-738c4b3dc322

---------

Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
2026-02-25 13:55:41 +01:00
Lucas Bordeau 2c02f2c499 Handle SSE event for View and ViewField (#18218)
This PR implements a first working version of SSE for View and
ViewField.

We could improve the logic but we re-use the same actual codepath for
refreshing the views of an object.

This does not handle filters and sorts for now.

# Demo 


https://github.com/user-attachments/assets/73367f3a-a5fc-4b06-a2e2-fad3b99deec7
2026-02-25 13:43:56 +01:00
Charles Bochet 121788c42f Fully deprecate old recoil (#18210)
## Summary

Removes the `recoil` dependency entirely from `package.json` and
`twenty-front/package.json`, completing the migration to Jotai as the
sole state management library.

Removes all Recoil infrastructure: `RecoilRoot` wrapper from `App.tsx`
and test decorators, `RecoilDebugObserver`, Recoil-specific ESLint rules
(`use-getLoadable-and-getValue-to-get-atoms`,
`useRecoilCallback-has-dependency-array`), and legacy Recoil utility
hooks/types (`useRecoilComponentState`, `useRecoilComponentValue`,
`createComponentState`, `createFamilyState`, `getSnapshotValue`,
`cookieStorageEffect`, `localStorageEffect`, etc.).

Renames all `V2`-suffixed Jotai state files and types to their canonical
names (e.g., `ComponentStateV2` -> `ComponentState`,
`agentChatInputStateV2` -> `agentChatInputState`, `SelectorCallbacksV2`
-> `SelectorCallbacks`), and removes the now-redundant V1 counterparts.

Updates ~433 files across the codebase to use the renamed Jotai imports,
remove Recoil imports, and clean up test wrappers (`RecoilRootDecorator`
-> `JotaiRootDecorator`).
2026-02-25 12:26:42 +01:00
Abdul Rahman ecb7270a7b fix: delete orphan favorites before 1.18 migration (#18219) 2026-02-25 11:30:39 +01:00
martmull e365b546d9 Deploy new cli version (#18216)
as title
2026-02-25 10:58:21 +01:00
martmull f080b952eb Add manifest integration tests (#18203)
- add integration test on sync manifest endpoint
- fix invalid standard uuid + migration command
2026-02-25 10:37:50 +01:00
martmull b84a8588be Fix function logs (#18215)
as title
2026-02-25 10:36:19 +01:00
martmull e6a415b438 Fix twenty zapier (#18191)
as title
2026-02-25 10:35:46 +01:00
Charles Bochet d81f006979 Rename Jotai state hooks and utilities to consistent Atom-based naming (#18209)
## Summary

Rename all Jotai-based state management hooks and utilities to a
consistent naming convention that:
1. Removes legacy Recoil naming (`useRecoilXxxV2`, `createXxxV2`)
2. Always includes `Atom` in the name to distinguish from jotai native
hooks
3. Follows a consistent ordering: `Atom` → `Component` → `Family` →
`Type` (`State`/`Selector`)
4. Includes the type qualifier (`State` or `Selector`) in all
value-reading hooks to avoid naming conflicts with jotai's native
`useAtomValue`

## Naming Convention

**Hooks**:
`use[Set]Atom[Component][Family][State|Selector][Value|State|CallbackState]`
**Utils**: `createAtom[Component][Family][State|Selector]`

## Changes

### Hooks renamed (definition files + all usages across ~1,500 files):

| Old Name (Recoil) | Intermediate (Atom) | Final Name |
|---|---|---|
| `useRecoilValueV2` | `useAtomValue` | **`useAtomStateValue`** |
| `useRecoilStateV2` | `useAtomState` | `useAtomState` |
| `useSetRecoilStateV2` | `useSetAtomState` | `useSetAtomState` |
| `useFamilyRecoilValueV2` | `useFamilyAtomValue` |
**`useAtomFamilyStateValue`** |
| `useSetFamilyRecoilStateV2` | `useSetFamilyAtomState` |
**`useSetAtomFamilyState`** |
| `useFamilySelectorValueV2` | `useFamilySelectorValue` |
**`useAtomFamilySelectorValue`** |
| `useFamilySelectorStateV2` | `useFamilySelectorState` |
**`useAtomFamilySelectorState`** |
| `useRecoilComponentValueV2` | `useAtomComponentValue` |
**`useAtomComponentStateValue`** |
| `useRecoilComponentStateV2` | `useAtomComponentState` |
`useAtomComponentState` |
| `useSetRecoilComponentStateV2` | `useSetAtomComponentState` |
`useSetAtomComponentState` |
| `useRecoilComponentFamilyValueV2` | `useAtomComponentFamilyValue` |
**`useAtomComponentFamilyStateValue`** |
| `useRecoilComponentFamilyStateV2` | `useAtomComponentFamilyState` |
`useAtomComponentFamilyState` |
| `useSetRecoilComponentFamilyStateV2` |
`useSetAtomComponentFamilyState` | `useSetAtomComponentFamilyState` |
| `useRecoilComponentSelectorValueV2` | `useAtomComponentSelectorValue`
| `useAtomComponentSelectorValue` |
| `useRecoilComponentFamilySelectorValueV2` |
`useAtomComponentFamilySelectorValue` |
`useAtomComponentFamilySelectorValue` |
| `useRecoilComponentStateCallbackStateV2` |
`useAtomComponentStateCallbackState` |
`useAtomComponentStateCallbackState` |
| `useRecoilComponentSelectorCallbackStateV2` |
`useAtomComponentSelectorCallbackState` |
`useAtomComponentSelectorCallbackState` |
| `useRecoilComponentFamilyStateCallbackStateV2` |
`useAtomComponentFamilyStateCallbackState` |
`useAtomComponentFamilyStateCallbackState` |
| `useRecoilComponentFamilySelectorCallbackStateV2` |
`useAtomComponentFamilySelectorCallbackState` |
`useAtomComponentFamilySelectorCallbackState` |

### Utilities renamed:

| Old Name (Recoil) | Final Name |
|---|---|
| `createStateV2` | **`createAtomState`** |
| `createFamilyStateV2` | **`createAtomFamilyState`** |
| `createSelectorV2` | **`createAtomSelector`** |
| `createFamilySelectorV2` | **`createAtomFamilySelector`** |
| `createWritableSelectorV2` | **`createAtomWritableSelector`** |
| `createWritableFamilySelectorV2` |
**`createAtomWritableFamilySelector`** |
| `createComponentStateV2` | **`createAtomComponentState`** |
| `createComponentFamilyStateV2` | **`createAtomComponentFamilyState`**
|
| `createComponentSelectorV2` | **`createAtomComponentSelector`** |
| `createComponentFamilySelectorV2` |
**`createAtomComponentFamilySelector`** |

## Details

- All definition files renamed to match new convention
- All import paths and usages updated across the entire `twenty-front`
package
- Jotai's native `useAtomValue` is aliased as `useJotaiAtomValue` in the
wrapper hook to avoid collision
- Legacy Recoil files in `state/utils/` and `component-state/utils/`
left untouched (separate naming scope)
- Typecheck passes cleanly
2026-02-25 01:55:46 +01:00
Charles Bochet eaf9fe27b2 Improve Jotai work (#18205) 2026-02-24 20:38:30 +01:00
Baptiste Devessier b339c93699 Fix app env var fetching (#18199) 2026-02-24 20:20:33 +01:00
nitin 887371054a Host-remote refresh token implementation (#18044) 2026-02-24 19:37:29 +01:00
Raphaël Bosi b56f85f36a Allow video, audio and iFrame in front components (#18200)
Allow video, audio and iFrame in front components

+ fix css style properties starting with `--`
2026-02-24 19:04:52 +01:00
github-actions[bot] 1ed13182d0 i18n - translations (#18197)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-24 18:10:28 +01:00
sonarly[bot] 20d997bc5a Fix: Password validation requires data and hash arguments (#18189)
## Automated fix for [bug 5360](https://sonarly.com/issue/5360?type=bug)

**Severity:** `critical`

### Summary
When an existing user created via OAuth (with no password hash) attempts
to sign in through the signInUp password flow, their null passwordHash
is passed directly to bcrypt.compare, which throws an unhandled 'data
and hash arguments required' error instead of a user-friendly message.

### User Impact
Users who originally registered via Google or Microsoft OAuth and then
attempt to sign in using email/password through the signInUp flow cannot
authenticate. They see a server error instead of a helpful message like
'User was not created with email/password'. This blocks the user from
accessing the CRM entirely.

### Root Cause
Proximate cause: bcrypt.compare() at auth.util.ts:19 throws 'data and
hash arguments required' because the passwordHash argument is null.

1. Why did bcrypt throw? Because compareHash() was called with a null
passwordHash value. The compareHash function at auth.util.ts:19 passes
its arguments directly to bcrypt.compare without any null check.

2. Why was passwordHash null? Because SignInUpService.validatePassword()
at sign-in-up.service.ts:154 received a null passwordHash from
AuthService.validatePassword() at auth.service.ts:232. The value
userData.existingUser.passwordHash is null for users who were originally
created via an OAuth provider (Google, Microsoft) and never set a
password.

3. Why was there no null check before calling bcrypt? Because
AuthService.validatePassword() at line 229-233 does not check whether
userData.existingUser.passwordHash is null before passing it to
signInUpService.validatePassword(). The TypeScript type annotation says
passwordHash is string, but the UserEntity column is declared nullable:
true (user.entity.ts:73), so the runtime value can be null despite the
type system.

4. Why does this code path lack the null check when another path has it?
The validateLoginWithPassword() method at auth.service.ts:177 correctly
checks if (!user.passwordHash) and throws a user-friendly AuthException
'Incorrect login method'. This guard was added by Thomas Trompette on
2024-08-07 (commit 2abb6adb61). However, the signInUp flow's
validatePassword() method was introduced later by Antoine Moreaux on
2025-03-17 (commit bda835b9f8) without replicating this same defensive
check.

5. Why was this not caught? The signInUp validatePassword method was
written assuming the existingUser would always have a passwordHash when
the auth provider is Password. This assumption is incorrect because the
signInUp flow can match an existing OAuth-created user (who has no
passwordHash) when someone tries to sign up with email/password using an
email already registered via OAuth. No test covers this cross-provider
scenario in the signInUp path.

Root cause: The AuthService.validatePassword() method
(auth.service.ts:229-233) in the signInUp code path is missing a null
guard on userData.existingUser.passwordHash before passing it to
bcrypt.compare. The fix should check for null/undefined passwordHash and
throw a descriptive AuthException, mirroring the existing guard in
validateLoginWithPassword at line 177.

**Introduced by:** Antoine Moreaux on 2025-03-17 in commit
[`bda835b`](https://github.com/twentyhq/twenty/commit/bda835b9f82)

### Suggested Fix
Added a null check on userData.existingUser.passwordHash inside the
validatePassword private method of AuthService, immediately before
calling signInUpService.validatePassword. When passwordHash is null or
undefined (i.e. the user was created via OAuth and never set a
password), an AuthException with code INVALID_INPUT is thrown with the
user-friendly message 'User was not created with email/password'. This
mirrors the identical guard that already exists in
validateLoginWithPassword at line 177, preventing bcrypt.compare from
receiving a null argument and crashing with an unhandled error.

### Evidence
- **Code:**
[packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts:229
- if (userData.type === 'existingUser')
{](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts#L229)

---
*Generated by [Sonarly](https://sonarly.com)*

Co-authored-by: Sonarly Claude Code <claude-code@sonarly.com>
2026-02-24 17:52:53 +01:00
Charles Bochet 0f2af6a6cb Jotai 13 (#18178)
## Recoil to Jotai Migration — PR 13: Workflow, Page Layout, AI,
Activities, Settings, SSE & Remaining Modules

### Summary

This is the 13th PR in the [14-PR migration
series](packages/twenty-front/src/modules/ui/utilities/state/jotai/MIGRATION_PLAN.md)
to replace Recoil with Jotai as the state management library in
`twenty-front`. This PR migrates the remaining consumer code across all
modules — updating ~1,076 files with ~10k insertions/deletions.

### What changed

**New Jotai infrastructure (13 new files):**
- `createComponentSelectorV2` and `createComponentFamilySelectorV2` —
instance-scoped derived atoms, replacing Recoil's component selectors
- 6 new hooks: `useRecoilComponentSelectorValueV2`,
`useRecoilComponentFamilySelectorValueV2`,
`useRecoilComponentSelectorCallbackStateV2`,
`useRecoilComponentFamilySelectorCallbackStateV2`,
`useRecoilComponentStateCallbackStateV2`,
`useRecoilComponentFamilyStateCallbackStateV2`
- New types: `ComponentSelectorV2`, `ComponentFamilySelectorV2`,
`SelectorCallbacksV2`
- Extended `buildGetHelper` to support the new selector patterns

**State definition migrations:**
- `createState()` → `createStateV2()` (Jotai `atom()`)
- `createFamilyState()` → `createFamilyStateV2()` (Jotai `atom()` with
family cache)
- Recoil `selector`/`selectorFamily` → Jotai derived `atom()` via V2
utilities

**Hook migrations (~2,400 removed, ~1,545 added V2 equivalents):**
- `useRecoilValue` → `useRecoilValueV2`
- `useRecoilState` → `useRecoilStateV2`
- `useSetRecoilState` → `useSetRecoilStateV2`
- `useRecoilCallback` → `useCallback` + `jotaiStore.get/set`
- `useRecoilComponentValue` → `useRecoilComponentValueV2`
- `useSetRecoilComponentState` → `useSetRecoilComponentStateV2`
- `useRecoilComponentFamilyValue` → `useRecoilComponentFamilyValueV2` /
`useFamilySelectorValueV2`
- ~397 direct `import from 'recoil'` removed

**Deleted files (9 files):**
- `dateLocaleState.ts` — consolidated
- `currentAIChatThreadTitleStateV2.ts` — renamed to replace the original
- `isCommandMenuOpenedState.ts` — removed
- `filesFieldUploadState.ts` — replaced by V2
- `recordStoreFamilySelector.ts`, `recordStoreFieldValueSelector.ts`,
`recordStoreRecordsSelector.ts` — replaced by V2 equivalents
- `settingsAllRolesSelector.ts` — replaced by `useSettingsAllRoles` hook
- `settingsRoleIdsStateV2.ts` — consolidated

**Modules migrated:**
- Workflow (diagram, steps, variables, filters)
- Page Layout (widgets, fields, graph, tabs)
- AI (chat, agents, browsing context)
- Activities (rich text editor, targets, timeline)
- Action Menu (single/multiple record actions)
- Command Menu (navigation, history, pages, workflow)
- Context Store
- Record Board, Record Table, Record Calendar, Record Index
- Record Field, Record Filter, Record Sort, Record Group
- Record Drag, Record Picker, Record Store
- Views (view bar, view picker, filters, sorts)
- Settings (roles, permissions, SSO, security, data model, developers,
domains)
- SSE (event streams, subscriptions)
- Spreadsheet Import
- Auth, Favorites, Front Components, Information Banner
2026-02-24 17:37:01 +01:00
Paul Rastoin c6ec764b23 Fix ci-server ci (#18180) 2026-02-24 12:51:46 +01:00
github-actions[bot] 959cbfb725 i18n - translations (#18183)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-24 12:08:42 +01:00
Félix Malfait 458a61ed30 Change runner from depot-ubuntu-24.04 to ubuntu-latest (#18182) 2026-02-24 10:17:38 +01:00
Félix Malfait 9a3852bf04 feat: add two-layer AI model availability filtering (#18170)
## Summary

- **Admin-level filtering**: New AI tab in admin panel with server-wide
model availability controls (whitelist/blacklist via
`AI_AUTO_ENABLE_NEW_MODELS`, `AI_DISABLED_MODEL_IDS`,
`AI_ENABLED_MODEL_IDS` config variables). Dedicated
`setAdminAiModelEnabled` mutation replaces frontend config-variable
manipulation. Filter dropdown to show/hide unconfigured and deprecated
models.
- **Workspace-level filtering**: Per-workspace controls with "Use best
models only" mode (curated list backed by `isRecommended` flag), or
custom whitelist/blacklist. Separate Smart/Fast model selectors with
"Best (...)" virtual options.
- **Security enforcement**: Both layers enforced at every backend
execution point — workspace update, agent create/update, chat execution.
Model ID validated against known models before config mutation. All
admin endpoints protected by `AdminPanelGuard`.

## Changes

### Backend (`twenty-server`)
- New config variables for admin-level model filtering
- `AiModelRegistryService`: `getAllModelsWithStatus()`,
`setModelAdminEnabled()` with model ID validation,
`isModelAdminAllowed()`
- `AdminPanelResolver`: `getAdminAiModels` query,
`setAdminAiModelEnabled` mutation
- `WorkspaceEntity`: new fields (`autoEnableNewAiModels`,
`disabledAiModelIds`, `enabledAiModelIds`, `useRecommendedModels`)
- `WorkspaceService`: model validation on `smartModel`/`fastModel`
updates
- `AgentResolver`: model availability checks on create/update
- `isModelAllowedByWorkspace` centralized utility
- `isRecommended` flag on model definitions
- Two TypeORM migrations

### Frontend (`twenty-front`)
- New `SettingsAdminAI` component with search, filter dropdown
(unconfigured/deprecated), and model toggle cards
- AI tab added to admin panel navigation
- `useWorkspaceAiModelAvailability` hook for workspace-level filtering
- `SettingsAIModelsTab` redesigned: merged sections, "Use best models
only" toggle, conditional available models list
- `getModelIcon`/`getModelProviderLabel` shared utilities with GraphQL
enum casing normalization
- Updated generated GraphQL types and mock data

## Test plan
- [ ] Toggle models on/off in admin panel AI tab and verify they
appear/disappear in workspace settings
- [ ] Enable "Use best models only" in workspace settings and verify
only recommended models are selectable
- [ ] Disable recommended mode and verify whitelist/blacklist toggles
work correctly
- [ ] Verify deprecated models hidden by default, shown greyed out when
filter enabled
- [ ] Verify unconfigured models hidden by default, shown disabled when
filter enabled
- [ ] Try setting a disabled model as Smart/Fast model — should be
rejected
- [ ] Try creating an agent with a disabled model — should be rejected
- [ ] Verify admin panel AI tab requires admin access


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 10:14:24 +01:00
github-actions[bot] c369baf63a i18n - translations (#18176)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-02-24 10:07:16 +01:00
Paul Rastoin 9e8024a07a Restore depot (#18179) 2026-02-24 09:59:54 +01:00
a827681306 df196b4a03 fix: use default Apollo client for AI chat file upload (#18158)
## Summary

Fix file uploads in AI chat (Agent Chat) failing with "Failed to upload
file" for all file types.

## Root Cause

`useAIChatFileUpload.ts` was using `useApolloCoreClient()` which points
to the `/graphql` (workspace) endpoint. However, `FileUploadResolver` is
decorated with `@MetadataResolver()`, meaning `uploadFile` is only
registered on the `/metadata` endpoint.

## Fix

Replace `useApolloCoreClient()` with `useApolloClient()` from
`@apollo/client`, which is the default Apollo client pointing to the
`/metadata` endpoint. This is consistent with how
`useUploadAttachmentFile.tsx` handles file uploads.

## Changes

- Replaced `useApolloCoreClient` import with `useApolloClient` from
`@apollo/client`
- Updated the client variable to use the default Apollo client
- Removed unused `useApolloCoreClient` import

Fixes #18153
2026-02-24 09:54:29 +01:00
3856 changed files with 146144 additions and 81992 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ This directory contains Twenty's development guidelines and best practices in th
### React Development
- **react-general-guidelines.mdc** - Core React development principles (Auto-attached to React files)
- **react-state-management.mdc** - State management approaches with Recoil (Auto-attached to state files)
- **react-state-management.mdc** - State management approaches with Jotai (Auto-attached to state files)
### Testing & Quality
- **testing-guidelines.mdc** - Testing strategies and best practices (Auto-attached to test files)
+1 -1
View File
@@ -7,7 +7,7 @@ alwaysApply: true
# Twenty Architecture
## Tech Stack
- **Frontend**: React 18, TypeScript, Recoil, Styled Components, Vite
- **Frontend**: React 18, TypeScript, Jotai, Styled Components, Vite
- **Backend**: NestJS, TypeORM, PostgreSQL, Redis, GraphQL
- **Monorepo**: Nx workspace with yarn
+33 -12
View File
@@ -4,16 +4,20 @@ alwaysApply: false
---
# React State Management
## Recoil Patterns
## Jotai Patterns
```typescript
// ✅ Atoms for primitive state
export const currentUserState = atom<User | null>({
// ✅ Atoms for primitive state (use createAtomState for keyed state with optional persistence)
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const currentUserState = createAtomState<User | null>({
key: 'currentUserState',
default: null,
defaultValue: null,
});
// ✅ Selectors for derived state
export const userDisplayNameSelector = selector({
// ✅ Derived atoms for computed state (use createAtomSelector)
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
export const userDisplayNameSelector = createAtomSelector({
key: 'userDisplayNameSelector',
get: ({ get }) => {
const user = get(currentUserState);
@@ -21,13 +25,30 @@ export const userDisplayNameSelector = selector({
},
});
// ✅ Atom families for dynamic atoms
export const userByIdState = atomFamily<User | null, string>({
// ✅ Atom factory pattern for dynamic atoms (use createAtomFamilyState)
import { createAtomFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomFamilyState';
export const userByIdState = createAtomFamilyState<User | null, string>({
key: 'userByIdState',
default: null,
defaultValue: null,
});
```
## Jotai Hooks
```typescript
// useAtomState - read and write
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
// useAtomStateValue - read only
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
// useSetAtomState - write only
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
```
## Provider
Jotai works without a Provider by default. For scoped stores or testing, use `Provider` from `jotai`.
## Local State Guidelines
```typescript
// ✅ Multiple useState for unrelated state
@@ -74,7 +95,7 @@ const increment = useCallback(() => {
```
## Performance Tips
- Use atom families for dynamic data collections
- Implement proper selector caching
- Avoid heavy computations in selectors
- Use atom factory pattern (createAtomFamilyState) for dynamic data collections
- Derived atoms (createAtomSelector) are automatically memoized by Jotai
- Avoid heavy computations in derived atoms
- Batch state updates when possible
+12 -12
View File
@@ -771,16 +771,16 @@ jobs:
kill $(cat /tmp/main-server.pid) || true
fi
- name: Upload API specifications and diffs
if: always()
uses: actions/upload-artifact@v4
with:
name: api-specifications-and-diffs
path: |
/tmp/main-server.log
/tmp/current-server.log
*-api.json
*-schema-introspection.json
*-diff.md
*-diff.json
# - name: Upload API specifications and diffs
# if: always()
# uses: actions/upload-artifact@v4
# with:
# name: api-specifications-and-diffs
# path: |
# /tmp/main-server.log
# /tmp/current-server.log
# *-api.json
# *-schema-introspection.json
# *-diff.md
# *-diff.json
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 10
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
+71 -68
View File
@@ -14,8 +14,8 @@ concurrency:
env:
# restore-cache action adds 'v4-' prefix and '-<branch>-<sha>' suffix to the key
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-ubuntu-latest-8-cores-runner
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-ubuntu-latest-8-cores-runner-${{ github.ref_name }}-${{ github.sha }}
STORYBOOK_BUILD_CACHE_KEY_FOR_RESTORE_ACTION: storybook-build-depot-ubuntu-24.04-8-runner
STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION: v4-storybook-build-depot-ubuntu-24.04-8-runner-${{ github.ref_name }}-${{ github.sha }}
jobs:
changed-files-check:
@@ -42,7 +42,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
env:
REACT_APP_SERVER_BASE_URL: http://localhost:3000
steps:
@@ -68,7 +68,7 @@ jobs:
key: ${{ env.STORYBOOK_BUILD_CACHE_KEY_FOR_SAVE_ACTION }}
front-sb-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
needs: front-sb-build
strategy:
fail-fast: false
@@ -98,52 +98,52 @@ jobs:
run: npx nx reset:env twenty-front
- name: Run storybook tests
run: npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }}
- name: Rename coverage file
run: |
if [ -f "packages/twenty-front/coverage/storybook/coverage-final.json" ]; then
mv packages/twenty-front/coverage/storybook/coverage-final.json packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
else
echo "Error: coverage-final.json not found"
ls -la packages/twenty-front/coverage/storybook/ || echo "Coverage directory does not exist"
exit 1
fi
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
retention-days: 1
name: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-${{ matrix.shard }}
path: packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
merge-reports-and-check-coverage:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04
needs: front-sb-test
env:
PATH_TO_COVERAGE: packages/twenty-front/coverage/storybook
strategy:
matrix:
storybook_scope: [modules, pages, performance]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- uses: actions/download-artifact@v4
with:
pattern: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-*
merge-multiple: true
path: coverage-artifacts
- name: Merge coverage reports
run: |
mkdir -p ${{ env.PATH_TO_COVERAGE }}
npx nyc merge coverage-artifacts ${{ env.PATH_TO_COVERAGE }}/coverage-storybook.json
- name: Checking coverage
run: npx nx storybook:coverage twenty-front --checkCoverage=true --configuration=${{ matrix.storybook_scope }}
# - name: Rename coverage file
# run: |
# if [ -f "packages/twenty-front/coverage/storybook/coverage-final.json" ]; then
# mv packages/twenty-front/coverage/storybook/coverage-final.json packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
# else
# echo "Error: coverage-final.json not found"
# ls -la packages/twenty-front/coverage/storybook/ || echo "Coverage directory does not exist"
# exit 1
# fi
# - name: Upload coverage artifact
# uses: actions/upload-artifact@v4
# with:
# retention-days: 1
# name: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-${{ matrix.shard }}
# path: packages/twenty-front/coverage/storybook/coverage-shard-${{matrix.shard}}.json
# merge-reports-and-check-coverage:
# timeout-minutes: 30
# runs-on: depot-ubuntu-24.04
# needs: front-sb-test
# env:
# PATH_TO_COVERAGE: packages/twenty-front/coverage/storybook
# strategy:
# matrix:
# storybook_scope: [modules, pages, performance]
# steps:
# - uses: actions/checkout@v4
# with:
# fetch-depth: 0
# - name: Install dependencies
# uses: ./.github/actions/yarn-install
# - uses: actions/download-artifact@v4
# with:
# pattern: coverage-artifacts-${{ matrix.storybook_scope }}-${{ github.run_id }}-*
# merge-multiple: true
# path: coverage-artifacts
# - name: Merge coverage reports
# run: |
# mkdir -p ${{ env.PATH_TO_COVERAGE }}
# npx nyc merge coverage-artifacts ${{ env.PATH_TO_COVERAGE }}/coverage-storybook.json
# - name: Checking coverage
# run: npx nx storybook:coverage twenty-front --checkCoverage=true --configuration=${{ matrix.storybook_scope }}
front-chromatic-deployment:
timeout-minutes: 30
if: false
needs: front-sb-build
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
env:
REACT_APP_SERVER_BASE_URL: http://127.0.0.1:3000
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
@@ -211,7 +211,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
env:
NODE_OPTIONS: "--max-old-space-size=10240"
steps:
@@ -229,12 +229,12 @@ jobs:
run: npx nx reset:env twenty-front
- name: Build frontend
run: npx nx build twenty-front
- name: Upload frontend build artifact
uses: actions/upload-artifact@v4
with:
name: frontend-build
path: packages/twenty-front/build
retention-days: 1
# - name: Upload frontend build artifact
# uses: actions/upload-artifact@v4
# with:
# name: frontend-build
# path: packages/twenty-front/build
# retention-days: 1
e2e-test:
runs-on: depot-ubuntu-24.04
needs: [changed-files-check-e2e, front-build]
@@ -296,15 +296,18 @@ jobs:
cp packages/twenty-front/.env.example packages/twenty-front/.env
npx nx reset:env:e2e-testing-server twenty-server
- name: Download frontend build artifact
if: needs.front-build.result == 'success'
uses: actions/download-artifact@v4
with:
name: frontend-build
path: packages/twenty-front/build
# - name: Download frontend build artifact
# if: needs.front-build.result == 'success'
# uses: actions/download-artifact@v4
# with:
# name: frontend-build
# path: packages/twenty-front/build
- name: Build frontend (if not available from front-build)
if: needs.front-build.result == 'skipped'
# - name: Build frontend (if not available from front-build)
# if: needs.front-build.result == 'skipped'
# run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build frontend
run: NODE_ENV=production NODE_OPTIONS="--max-old-space-size=10240" npx nx build twenty-front
- name: Build server
@@ -336,12 +339,12 @@ jobs:
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: packages/twenty-e2e-testing/run_results/
retention-days: 30
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: packages/twenty-e2e-testing/run_results/
# retention-days: 30
ci-front-status-check:
if: always() && !cancelled()
@@ -352,7 +355,7 @@ jobs:
changed-files-check,
front-task,
front-build,
merge-reports-and-check-coverage,
# merge-reports-and-check-coverage,
front-sb-test,
front-sb-build,
]
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
tasks: ${{ matrix.task }}
sdk-e2e-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
needs: [changed-files-check, sdk-test]
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
+3 -3
View File
@@ -31,7 +31,7 @@ jobs:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
services:
postgres:
image: twentycrm/twenty-postgres-spilo
@@ -143,7 +143,7 @@ jobs:
key: ${{ steps.restore-server-setup-cache.outputs.cache-primary-key }}
server-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
needs: server-setup
steps:
- name: Fetch custom Github Actions and base branch history
@@ -164,7 +164,7 @@ jobs:
server-integration-test:
timeout-minutes: 30
runs-on: ubuntu-latest-8-cores
runs-on: depot-ubuntu-24.04-8
needs: server-setup
strategy:
fail-fast: false
+60
View File
@@ -0,0 +1,60 @@
name: CI Zapier
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/twenty-zapier/**
packages/twenty-server/**
!packages/twenty-zapier/package.json
!packages/twenty-zapier/CHANGELOG.md
zapier-test:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: depot-ubuntu-24.04
strategy:
matrix:
task: [lint, typecheck, test, validate]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build
run: npx nx build twenty-zapier
- name: Run ${{ matrix.task }} task
uses: ./.github/actions/nx-affected
with:
tag: scope:zapier
tasks: ${{ matrix.task }}
ci-zapier-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: depot-ubuntu-24.04
needs: [changed-files-check, zapier-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+1 -1
View File
@@ -11,7 +11,7 @@ on:
jobs:
preview-environment:
timeout-minutes: 310
runs-on: depot-ubuntu-24.04
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
+2 -2
View File
@@ -90,7 +90,7 @@ npx nx run twenty-front:graphql:generate --configuration=metadata
## Architecture Overview
### Tech Stack
- **Frontend**: React 18, TypeScript, Recoil (state management), Emotion (styling), Vite
- **Frontend**: React 18, TypeScript, Jotai (state management), Emotion (styling), Vite
- **Backend**: NestJS, TypeORM, PostgreSQL, Redis, GraphQL (with GraphQL Yoga)
- **Monorepo**: Nx workspace managed with Yarn 4
@@ -138,7 +138,7 @@ packages/
- Multi-line comments use multiple `//` lines, not `/** */`
### State Management
- **Recoil** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
- **Jotai** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
- Component-specific state with React hooks (`useState`, `useReducer` for complex logic)
- GraphQL cache managed by Apollo Client
- Use functional state updates: `setState(prev => prev + 1)`
+1 -1
View File
@@ -109,7 +109,7 @@ Below are a few features we have implemented to date:
- [TypeScript](https://www.typescriptlang.org/)
- [Nx](https://nx.dev/)
- [NestJS](https://nestjs.com/), with [BullMQ](https://bullmq.io/), [PostgreSQL](https://www.postgresql.org/), [Redis](https://redis.io/)
- [React](https://reactjs.org/), with [Recoil](https://recoiljs.org/), [Emotion](https://emotion.sh/) and [Lingui](https://lingui.dev/)
- [React](https://reactjs.org/), with [Jotai](https://jotai.org/), [Emotion](https://emotion.sh/) and [Lingui](https://lingui.dev/)
+4
View File
@@ -83,6 +83,10 @@ export default [
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:zapier'],
},
],
},
],
+1 -1
View File
@@ -126,7 +126,7 @@
"configurations": {
"ci": {
"ci": true,
"maxWorkers": 3
"maxWorkers": 1
},
"coverage": {
"coverageReporters": ["lcov", "text"]
+8 -11
View File
@@ -48,8 +48,7 @@
"react-responsive": "^9.0.2",
"react-router-dom": "^6.4.4",
"react-tooltip": "^5.13.1",
"recoil": "^0.7.7",
"remark-gfm": "^3.0.1",
"remark-gfm": "^4.0.1",
"rxjs": "^7.2.0",
"semver": "^7.5.4",
"slash": "^5.1.0",
@@ -84,11 +83,11 @@
"@sentry/types": "^8",
"@storybook-community/storybook-addon-cookie": "^5.0.0",
"@storybook/addon-coverage": "^3.0.0",
"@storybook/addon-docs": "^10.1.11",
"@storybook/addon-links": "^10.1.11",
"@storybook/addon-vitest": "^10.1.11",
"@storybook/addon-docs": "^10.2.13",
"@storybook/addon-links": "^10.2.13",
"@storybook/addon-vitest": "^10.2.13",
"@storybook/icons": "^2.0.1",
"@storybook/react-vite": "^10.1.11",
"@storybook/react-vite": "^10.2.13",
"@storybook/test-runner": "^0.24.2",
"@stylistic/eslint-plugin": "^1.5.0",
"@swc-node/register": "1.11.1",
@@ -160,7 +159,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^10.1.11",
"eslint-plugin-storybook": "^10.2.13",
"eslint-plugin-unicorn": "^56.0.1",
"eslint-plugin-unused-imports": "^3.0.0",
"http-server": "^14.1.1",
@@ -176,9 +175,9 @@
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
"source-map-support": "^0.5.20",
"storybook": "^10.1.11",
"storybook": "^10.2.13",
"storybook-addon-mock-date": "2.0.0",
"storybook-addon-pseudo-states": "^10.1.11",
"storybook-addon-pseudo-states": "^10.2.13",
"supertest": "^6.1.3",
"ts-jest": "^29.1.1",
"ts-loader": "^9.2.3",
@@ -202,8 +201,6 @@
"type-fest": "4.10.1",
"typescript": "5.9.2",
"graphql-redis-subscriptions/ioredis": "^5.6.0",
"prosemirror-view": "1.40.0",
"prosemirror-transform": "1.10.4",
"@lingui/core": "5.1.2",
"@types/qs": "6.9.16"
},
+6 -6
View File
@@ -31,10 +31,6 @@ See Twenty application documentation https://docs.twenty.com/developers/extend/c
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# If you don't use yarn@4
corepack enable
yarn install
# Get help and list all available commands
yarn twenty help
@@ -45,7 +41,7 @@ yarn twenty auth:login
yarn twenty entity:add
# Start dev mode: watches, builds, and syncs local changes to your workspace
# (also auto-generates a typed API client in node_modules/twenty-sdk/generated)
# (also auto-generates typed API clients — CoreApiClient and MetadataApiClient — in node_modules/twenty-sdk/generated)
yarn twenty app:dev
# Watch your application's function logs
@@ -54,6 +50,9 @@ yarn twenty function:logs
# Execute a function with a JSON payload
yarn twenty function:execute -n my-function -p '{"key": "value"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
@@ -96,6 +95,7 @@ In interactive mode, you can pick from:
**Core files (always created):**
- `application-config.ts` — Application metadata configuration
- `roles/default-role.ts` — Default role for logic functions
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
- TypeScript configuration, ESLint, package.json, .gitignore
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
@@ -114,7 +114,7 @@ In interactive mode, you can pick from:
- Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
- Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Types are autogenerated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`.
- Two typed API clients are autogenerated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
## Publish your application
+1 -1
View File
@@ -1,5 +1,5 @@
const jestConfig = {
displayName: 'twenty-cli',
displayName: 'create-twenty-app',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transformIgnorePatterns: ['../../node_modules/'],
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.6.0",
"version": "0.6.2",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -3,6 +3,9 @@
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/cli/__tests__/apps/rich-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
## Common Pitfalls
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
@@ -233,9 +233,9 @@ export class CreateAppCommand {
console.log('');
console.log(chalk.blue('Next steps:'));
console.log(chalk.gray(` cd ${dirName}`));
console.log(chalk.gray(` corepack enable # if you don't use yarn@4`));
console.log(chalk.gray(` yarn install # if you don't use yarn@4`));
console.log(chalk.gray(' yarn auth:login # Authenticate with Twenty'));
console.log(chalk.gray(' yarn app:dev # Start dev mode'));
console.log(
chalk.gray(' yarn twenty auth:login # Authenticate with Twenty'),
);
console.log(chalk.gray(' yarn twenty app:dev # Start dev mode'));
}
}
@@ -375,6 +375,18 @@ describe('copyBaseApplicationProject', () => {
),
),
).toBe(true);
// Install functions should always exist
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'pre-install.ts'),
),
).toBe(true);
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'post-install.ts'),
),
).toBe(true);
});
});
@@ -398,6 +410,18 @@ describe('copyBaseApplicationProject', () => {
await fs.pathExists(join(srcPath, 'roles', DEFAULT_ROLE_FILE_NAME)),
).toBe(true);
// Install functions should always exist (not gated by exampleOptions)
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'pre-install.ts'),
),
).toBe(true);
expect(
await fs.pathExists(
join(srcPath, 'logic-functions', 'post-install.ts'),
),
).toBe(true);
// Example files should not exist
expect(
await fs.pathExists(join(srcPath, 'objects', 'example-object.ts')),
@@ -676,4 +700,124 @@ describe('copyBaseApplicationProject', () => {
expect(content).toContain('position: 0');
});
});
describe('pre-install logic function', () => {
it('should create pre-install.ts with definePreInstallLogicFunction and typed payload', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: NO_EXAMPLES,
});
const preInstallPath = join(
testAppDirectory,
'src',
'logic-functions',
'pre-install.ts',
);
expect(await fs.pathExists(preInstallPath)).toBe(true);
const content = await fs.readFile(preInstallPath, 'utf8');
expect(content).toContain(
"import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'",
);
expect(content).toContain(
'export default definePreInstallLogicFunction({',
);
expect(content).toContain("name: 'pre-install'");
expect(content).toContain('timeoutSeconds: 300');
expect(content).toContain(
'const handler = async (payload: InstallLogicFunctionPayload): Promise<void>',
);
expect(content).toContain('payload.previousVersion');
// Verify it has a universalIdentifier (UUID format)
expect(content).toMatch(
/universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/,
);
});
it('should always create pre-install.ts regardless of example options', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: NO_EXAMPLES,
});
const preInstallPath = join(
testAppDirectory,
'src',
'logic-functions',
'pre-install.ts',
);
expect(await fs.pathExists(preInstallPath)).toBe(true);
});
});
describe('post-install logic function', () => {
it('should create post-install.ts with definePostInstallLogicFunction and typed payload', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: NO_EXAMPLES,
});
const postInstallPath = join(
testAppDirectory,
'src',
'logic-functions',
'post-install.ts',
);
expect(await fs.pathExists(postInstallPath)).toBe(true);
const content = await fs.readFile(postInstallPath, 'utf8');
expect(content).toContain(
"import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'",
);
expect(content).toContain(
'export default definePostInstallLogicFunction({',
);
expect(content).toContain("name: 'post-install'");
expect(content).toContain('timeoutSeconds: 300');
expect(content).toContain(
'const handler = async (payload: InstallLogicFunctionPayload): Promise<void>',
);
expect(content).toContain('payload.previousVersion');
// Verify it has a universalIdentifier (UUID format)
expect(content).toMatch(
/universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/,
);
});
it('should always create post-install.ts regardless of example options', async () => {
await copyBaseApplicationProject({
appName: 'my-test-app',
appDisplayName: 'My Test App',
appDescription: 'A test application',
appDirectory: testAppDirectory,
exampleOptions: NO_EXAMPLES,
});
const postInstallPath = join(
testAppDirectory,
'src',
'logic-functions',
'post-install.ts',
);
expect(await fs.pathExists(postInstallPath)).toBe(true);
});
});
});
@@ -97,6 +97,12 @@ export const copyBaseApplicationProject = async ({
});
}
await createDefaultPreInstallFunction({
appDirectory: sourceFolderPath,
fileFolder: 'logic-functions',
fileName: 'pre-install.ts',
});
await createDefaultPostInstallFunction({
appDirectory: sourceFolderPath,
fileFolder: 'logic-functions',
@@ -268,6 +274,36 @@ export default defineLogicFunction({
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createDefaultPreInstallFunction = async ({
appDirectory,
fileFolder,
fileName,
}: {
appDirectory: string;
fileFolder?: string;
fileName: string;
}) => {
const universalIdentifier = v4();
const content = `import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: '${universalIdentifier}',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
handler,
});
`;
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
};
const createDefaultPostInstallFunction = async ({
appDirectory,
fileFolder,
@@ -279,16 +315,14 @@ const createDefaultPostInstallFunction = async ({
}) => {
const universalIdentifier = v4();
const content = `import { defineLogicFunction } from 'twenty-sdk';
const content = `import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '${universalIdentifier}';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
export default definePostInstallLogicFunction({
universalIdentifier: '${universalIdentifier}',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
@@ -477,14 +511,12 @@ const createApplicationConfig = async ({
}) => {
const content = `import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '${v4()}',
displayName: '${displayName}',
description: '${description ?? ''}',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
`;
@@ -6,7 +6,13 @@ const execPromise = promisify(exec);
export const install = async (root: string) => {
try {
await execPromise('yarn', { cwd: root });
await execPromise('corepack enable', { cwd: root });
} catch (error: any) {
console.warn(chalk.yellow('corepack enabled failed:'), error.stderr);
}
try {
await execPromise('yarn install', { cwd: root });
} catch (error: any) {
console.error(chalk.red('yarn install failed:'), error.stdout);
}
@@ -7,9 +7,9 @@ This document outlines the best practices you should follow when working on the
## State management
React and Recoil handle state management in the codebase.
React and Jotai handle state management in the codebase.
### Use `useRecoilState` to store state
### Use Jotai atoms to store state
It's good practice to create as many atoms as you need to store your state.
@@ -20,13 +20,16 @@ It's better to use extra atoms than trying to be too concise with props drilling
</Warning>
```tsx
export const myAtomState = atom({
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',
default: 'default value',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useRecoilState(myAtomState);
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
@@ -43,7 +46,7 @@ export const MyComponent = () => {
Avoid using `useRef` to store state.
If you want to store state, you should use `useState` or `useRecoilState`.
If you want to store state, you should use `useState` or Jotai atoms with `useAtomState`.
See [how to manage re-renders](#managing-re-renders) if you feel like you need `useRef` to prevent some re-renders from happening.
@@ -83,8 +86,8 @@ You can apply the same for data fetching logic, with Apollo hooks.
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useRecoilState(dataState);
const [someDependency] = useRecoilState(someDependencyState);
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
@@ -96,9 +99,7 @@ export const PageComponent = () => {
};
export const App = () => (
<RecoilRoot>
<PageComponent />
</RecoilRoot>
<PageComponent />
);
```
@@ -106,14 +107,14 @@ export const App = () => (
// ✅ 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] = useRecoilState(dataState);
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useRecoilState(dataState);
const [someDependency] = useRecoilState(someDependencyState);
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
@@ -125,16 +126,16 @@ export const PageData = () => {
};
export const App = () => (
<RecoilRoot>
<>
<PageData />
<PageComponent />
</RecoilRoot>
</>
);
```
### Use recoil family states and recoil family selectors
### Use atom family states and selectors
Recoil family states and selectors are a great way to avoid re-renders.
Atom family states and selectors are a great way to avoid re-renders.
They are useful when you need to store a list of items.
@@ -83,9 +83,9 @@ See [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) for more de
### States
Contains the state management logic. [RecoilJS](https://recoiljs.org) handles this.
Contains the state management logic. [Jotai](https://jotai.org) handles this.
- Selectors: See [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) for more details.
- Selectors: Derived atoms (using `createAtomSelector`) compute values from other atoms and are automatically memoized.
React's built-in state management still handles state within a component.
@@ -52,7 +52,7 @@ The project has a clean and simple stack, with minimal boilerplate code.
- [React](https://react.dev/)
- [Apollo](https://www.apollographql.com/docs/)
- [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
- [Recoil](https://recoiljs.org/docs/introduction/core-concepts)
- [Jotai](https://jotai.org/)
- [TypeScript](https://www.typescriptlang.org/)
**Testing**
@@ -76,7 +76,7 @@ To avoid unnecessary [re-renders](/developers/contribute/capabilities/frontend-d
### State Management
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) handles state management.
[Jotai](https://jotai.org/) handles state management.
See [best practices](/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) for more information on state management.
@@ -159,7 +159,7 @@ export enum PageHotkeyScope {
}
```
Internally, the currently selected scope is stored in a Recoil state that is shared across the application :
Internally, the currently selected scope is stored in a Jotai atom that is shared across the application :
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
@@ -168,10 +168,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
});
```
But this Recoil state should never be handled manually ! We'll see how to use it in the next section.
But this atom should never be handled manually ! We'll see how to use it in the next section.
## How is it working internally?
We made a thin wrapper on top of [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) that makes it more performant and avoids unnecessary re-renders.
We also create a Recoil state to handle the hotkey scope state and make it available everywhere in the application.
We also create a Jotai atom to handle the hotkey scope state and make it available everywhere in the application.
@@ -31,13 +31,6 @@ Create a new app using the official scaffolder, then authenticate and start deve
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# If you don't use yarn@4
corepack enable
yarn install
# Authenticate using your API key (you'll be prompted)
yarn twenty auth:login
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty app:dev
```
@@ -67,6 +60,9 @@ yarn twenty function:logs
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the pre-install function
yarn twenty function:execute --preInstall
# Execute the post-install function
yarn twenty function:execute --postInstall
@@ -86,7 +82,7 @@ When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
- Copies a minimal base application into `my-twenty-app/`
- Adds a local `twenty-sdk` dependency and Yarn 4 configuration
- Creates config files and scripts wired to the `twenty` CLI
- Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
- Generates core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
@@ -113,6 +109,7 @@ my-twenty-app/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
│ └── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
@@ -124,7 +121,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
At a high level:
@@ -145,6 +142,8 @@ The SDK detects entities by parsing your TypeScript files for **`export default
|-----------------|-------------|
| `defineObject()` | Custom object definitions |
| `defineLogicFunction()` | Logic function definitions |
| `definePreInstallLogicFunction()` | Pre-install logic function (runs before installation) |
| `definePostInstallLogicFunction()` | Post-install logic function (runs after installation) |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | Role definitions |
| `defineField()` | Field extensions for existing objects |
@@ -170,7 +169,7 @@ export default defineObject({
Later commands will add more files and folders:
- `yarn twenty app:dev` will auto-generate a typed API client in `node_modules/twenty-sdk/generated` (typed Twenty client + workspace types).
- `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
- `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
## Authentication
@@ -219,6 +218,8 @@ The SDK provides helper functions for defining your app entities. As described i
| `defineApplication()` | Configure application metadata (required, one per app) |
| `defineObject()` | Define custom objects with fields |
| `defineLogicFunction()` | Define logic functions with handlers |
| `definePreInstallLogicFunction()` | Define a pre-install logic function (one per app) |
| `definePostInstallLogicFunction()` | Define a post-install logic function (one per app) |
| `defineFrontComponent()` | Define front components for custom UI |
| `defineRole()` | Configure role permissions and object access |
| `defineField()` | Extend existing objects with additional fields |
@@ -325,6 +326,7 @@ Every app has a single `application-config.ts` file that describes:
- **Who the app is**: identifiers, display name, and description.
- **How its functions run**: which role they use for permissions.
- **(Optional) variables**: keyvalue pairs exposed to your functions as environment variables.
- **(Optional) pre-install function**: a logic function that runs before the app is installed.
- **(Optional) post-install function**: a logic function that runs after the app is installed.
Use `defineApplication()` to define your application configuration:
@@ -333,7 +335,6 @@ Use `defineApplication()` to define your application configuration:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
@@ -349,7 +350,6 @@ export default defineApplication({
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -357,7 +357,7 @@ Notes:
- `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
- `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
- `defaultRoleUniversalIdentifier` must match the role file (see below).
- `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions).
- Pre-install and post-install functions are automatically detected during the manifest build. See [Pre-install functions](#pre-install-functions) and [Post-install functions](#post-install-functions).
#### Roles and permissions
@@ -430,10 +430,10 @@ Each function file uses `defineLogicFunction()` to export a configuration with a
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '~/generated';
import { CoreApiClient, type Person } from 'twenty-sdk/generated';
const handler = async (params: RoutePayload) => {
const client = new Twenty(); // generated typed client
const client = new CoreApiClient();
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
: 'Hello world';
@@ -490,6 +490,43 @@ Notes:
- The `triggers` array is optional. Functions without triggers can be used as utility functions called by other functions.
- You can mix multiple trigger types in a single function.
### Pre-install functions
A pre-install function is a logic function that runs automatically before your app is installed on a workspace. This is useful for validation tasks, prerequisite checks, or preparing workspace state before the main installation proceeds.
When you scaffold a new app with `create-twenty-app`, a pre-install function is generated for you at `src/logic-functions/pre-install.ts`:
```typescript
// src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: '<generated-uuid>',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
handler,
});
```
You can also manually execute the pre-install function at any time using the CLI:
```bash filename="Terminal"
yarn twenty function:execute --preInstall
```
Key points:
- Pre-install functions use `definePreInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
- The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs).
- Only one pre-install function is allowed per application. The manifest build will error if more than one is detected.
- The function's `universalIdentifier` is automatically set as `preInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`.
- The default timeout is set to 300 seconds (5 minutes) to allow for longer preparation tasks.
- Pre-install functions do not need triggers — they are invoked by the platform before installation or manually via `function:execute --preInstall`.
### Post-install functions
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
@@ -498,16 +535,14 @@ When you scaffold a new app with `create-twenty-app`, a post-install function is
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
export default definePostInstallLogicFunction({
universalIdentifier: '<generated-uuid>',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
@@ -515,17 +550,6 @@ export default defineLogicFunction({
});
```
The function is wired into your app by referencing its universal identifier in `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
You can also manually execute the post-install function at any time using the CLI:
```bash filename="Terminal"
@@ -533,8 +557,10 @@ yarn twenty function:execute --postInstall
```
Key points:
- Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
- The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
- Post-install functions use `definePostInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
- The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs).
- Only one post-install function is allowed per application. The manifest build will error if more than one is detected.
- The function's `universalIdentifier` is automatically set as `postInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`.
- The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
- Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
@@ -642,10 +668,10 @@ To mark a logic function as a tool, set `isTool: true` and provide a `toolInputS
```typescript
// src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import Twenty from '~/generated';
import { CoreApiClient } from 'twenty-sdk/generated';
const handler = async (params: { companyName: string; domain?: string }) => {
const client = new Twenty();
const client = new CoreApiClient();
const result = await client.mutation({
createTask: {
@@ -703,7 +729,7 @@ Key points:
Front components let you build custom React components that render within Twenty's UI. Use `defineFrontComponent()` to define components with built-in validation:
```typescript
// src/my-widget.front-component.tsx
// src/front-components/my-widget.tsx
import { defineFrontComponent } from 'twenty-sdk';
const MyWidget = () => {
@@ -725,14 +751,13 @@ export default defineFrontComponent({
Key points:
- Front components are React components that render in isolated contexts within Twenty.
- Use the `*.front-component.tsx` file suffix for automatic detection.
- The `component` field references your React component.
- Components are built and synced automatically during `yarn twenty app:dev`.
You can create new front components in two ways:
- **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new front component.
- **Manual**: Create a new `*.front-component.tsx` file and use `defineFrontComponent()`.
- **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
### Skills
@@ -768,18 +793,24 @@ You can create new skills in two ways:
- **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new skill.
- **Manual**: Create a new file and use `defineSkill()`, following the same pattern.
### Generated typed client
### Generated typed clients
The typed client is auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema. Use it in your functions:
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
- **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
- **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
```typescript
import Twenty from '~/generated';
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
const client = new Twenty();
const client = new CoreApiClient();
const { me } = await client.query({ me: { id: true, displayName: true } });
const metadataClient = new MetadataApiClient();
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
```
The client is re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
#### Runtime credentials in logic functions
@@ -795,17 +826,17 @@ Notes:
#### Uploading files
The generated `Twenty` client includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Because standard GraphQL clients do not support multipart file uploads natively, the client provides this dedicated method that implements the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec) under the hood.
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Because standard GraphQL clients do not support multipart file uploads natively, the client provides this dedicated method that implements the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec) under the hood.
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/generated';
import * as fs from 'fs';
const client = new Twenty();
const metadataClient = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
const uploadedFile = await client.uploadFile(
const uploadedFile = await metadataClient.uploadFile(
fileBuffer, // file contents as a Buffer
'invoice.pdf', // filename
'application/pdf', // MIME type (defaults to 'application/octet-stream')
@@ -835,7 +866,7 @@ uploadFile(
| `fieldMetadataUniversalIdentifier` | `string` | The `universalIdentifier` of the file-type field on your object |
Key points:
- The method sends the file to the **metadata endpoint** (not the main GraphQL endpoint), where the upload mutation is resolved.
- The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
- It uses the field's `universalIdentifier` (not its workspace-specific ID), so your upload code works across any workspace where your app is installed — consistent with how apps reference fields everywhere else.
- The returned `url` is a signed URL you can use to access the uploaded file.
@@ -11,7 +11,7 @@ title: كائنات مخصصة
## مخطط على مستوى عالي
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="مخطط على مستوى عالي" />
<img src="/images/docs/server/custom-object-schema.png" alt="مخطط على مستوى عالي" />
</div>
<br />
@@ -27,7 +27,7 @@ title: كائنات مخصصة
لإضافة كائن مخصص، سيقوم عضو مساحة العمل بالاستعلام عن واجهة برمجة التطبيقات /metadata. يقوم هذا بتحديث البيانات الوصفية وفقًا لذلك ويحسب مخطط GraphQL استنادًا إلى البيانات الوصفية، ويخزنها في ذاكرة GQL للاستخدام لاحقًا.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="استعلام واجهة برمجة التطبيقات /metadata لإضافة الكائنات المخصصة" />
<img src="/images/docs/server/add-custom-objects.jpeg" alt="استعلام واجهة برمجة التطبيقات /metadata لإضافة الكائنات المخصصة" />
</div>
<br />
@@ -35,5 +35,5 @@ title: كائنات مخصصة
لجلب البيانات، تتضمن العملية إجراء استعلامات من خلال نقطة النهاية /graphql وتمريرها من خلال محلل الاستعلام.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="استعلام نقطة النهاية /graphql لجلب البيانات" />
<img src="/images/docs/server/custom-object-schema.png" alt="استعلام نقطة النهاية /graphql لجلب البيانات" />
</div>
@@ -60,9 +60,11 @@ npx nx run twenty-server:command workspace:sync-metadata -f
```
<Warning>
سيؤدي هذا إلى إسقاط قاعدة البيانات وإعادة تشغيل الهجرات والبذور.
تأكد من عمل نسخة احتياطية لأي بيانات تريد الاحتفاظ بها قبل تشغيل هذا الأمر.
سيؤدي هذا إلى إسقاط قاعدة البيانات وإعادة تشغيل الهجرات والبذور.
تأكد من عمل نسخة احتياطية لأي بيانات تريد الاحتفاظ بها قبل تشغيل هذا الأمر.
</Warning>
## "التقنية المستخدمة"
@@ -43,7 +43,9 @@ cp .env.example .env
## التطوير
<Warning>
تأكد من تشغيل `yarn build` قبل أي أمر `zapier`.
تأكد من تشغيل `yarn build` قبل أي أمر `zapier`.
</Warning>
### تجربة
@@ -6,24 +6,29 @@ title: أفضل الممارسات
## إدارة الحالة
تقوم React و Recoil بإدارة الحالة في قاعدة الشيفرة.
تقوم React و Jotai بإدارة الحالة في قاعدة الشيفرة.
### استخدم `useRecoilState` لتخزين الحالة
### استخدم ذرات Jotai لتخزين الحالة
من الجيد إنشاء أكبر عدد ممكن من الذرات لتخزين الحالة الخاصة بك.
<Warning>
من الأفضل استخدام ذرات إضافية بدلاً من محاولة أن تكون مقتضبًا باستخدام تمرير الخصائص.
من الأفضل استخدام ذرات إضافية بدلاً من محاولة أن تكون مقتضبًا باستخدام تمرير الخصائص.
</Warning>
```tsx
export const myAtomState = atom({
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',
default: 'default value',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useRecoilState(myAtomState);
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
@@ -40,7 +45,7 @@ export const MyComponent = () => {
تجنب استخدام `useRef` لتخزين الحالة.
If you want to store state, you should use `useState` or `useRecoilState`.
إذا كنت ترغب في تخزين الحالة، يجب أن تستخدم `useState` أو ذرات Jotai مع `useAtomState`.
انظر [كيفية إدارة إعادة العرض](#managing-re-renders) إذا شعرت أنك بحاجة إلى `useRef` لمنع بعض إعادة العرض من الحدوث.
@@ -77,11 +82,11 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
يمكنك تطبيق نفس الشيء على منطق جلب البيانات، مع الخُطافات Apollo.
```tsx
// ❌ سيّئ، سيتسبب في إعادة التصيير حتى إذا لم تتغير البيانات،
// ❌ سيئ، سيتسبب في إعادة التصيير حتى إذا لم تتغير البيانات،
// لأن useEffect يحتاج إلى إعادة التقييم
export const PageComponent = () => {
const [data, setData] = useRecoilState(dataState);
const [someDependency] = useRecoilState(someDependencyState);
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
@@ -93,9 +98,7 @@ export const PageComponent = () => {
};
export const App = () => (
<RecoilRoot>
<PageComponent />
</RecoilRoot>
<PageComponent />
);
```
@@ -103,14 +106,14 @@ export const App = () => (
// ✅ جيّد، لن يتسبب في إعادة التصيير إذا لم تتغير البيانات،
// لأن useEffect يُعاد تقييمه في مكوّن شقيق آخر
export const PageComponent = () => {
const [data, setData] = useRecoilState(dataState);
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useRecoilState(dataState);
const [someDependency] = useRecoilState(someDependencyState);
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
@@ -122,16 +125,16 @@ export const PageData = () => {
};
export const App = () => (
<RecoilRoot>
<>
<PageData />
<PageComponent />
</RecoilRoot>
</>
);
```
### استخدم حالات عائلة Recoil ومحددات عائلة Recoil
### استخدم حالات عائلة الذرات والمحددات
حالات عائلة Recoil والمحددات تعتبر طريقة رائعة لتجنب إعادة العرض.
تُعد حالات عائلة الذرات والمحددات طريقة رائعة لتجنّب عمليات إعادة التصيير.
إنها مفيدة عندما تحتاج إلى تخزين قائمة من العناصر.
@@ -82,9 +82,9 @@ module1
### الحالات
تشمل منطق إدارة الحالة. [RecoilJS](https://recoiljs.org) يتولّى ذلك.
تشمل منطق إدارة الحالة. [Jotai](https://jotai.org) يتولّى ذلك.
* المحددات: انظر [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) لمزيد من التفاصيل.
* المحددات: الذرات المشتقة (باستخدام `createAtomSelector`) تحسب قيماً من ذرات أخرى وتخزن نتائجها في الذاكرة مؤقتاً تلقائياً.
لا تزال إدارة الحالة المدمجة في React تتولّى الحالة داخل المكوّن.
@@ -49,7 +49,7 @@ title: أوامر الواجهة الأمامية
* "[React](https://react.dev/)"
* "[Apollo](https://www.apollographql.com/docs/)"
* "[GraphQL Codegen](https://the-guild.dev/graphql/codegen)"
* "[Recoil](https://recoiljs.org/docs/introduction/core-concepts)"
* [Jotai](https://jotai.org/)
* "[TypeScript](https://www.typescriptlang.org/)"
**الاختبار**
@@ -73,7 +73,7 @@ To avoid unnecessary [re-renders](/l/ar/developers/contribute/capabilities/front
### "إدارة الحالة"
"[Recoil](https://recoiljs.org/docs/introduction/core-concepts) يتعامل مع إدارة الحالة."
[Jotai](https://jotai.org/) يتعامل مع إدارة الحالة.
"راجع [أفضل الممارسات](/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) لمزيد من المعلومات حول إدارة الحالة."
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
}
```
داخليًا، يتم تخزين النطاق المحدد حاليًا في حالة Recoil مشتركة عبر التطبيق:
داخليًا، يتم تخزين النطاق المحدد حاليًا في ذرة Jotai مشتركة عبر التطبيق:
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
});
```
لكن لا يجب التعامل مع هذه الحالة Recoil يدويًا! سنرى كيف يمكن استخدامها في القسم التالي.
لكن لا يجب التعامل مع هذه الذرة يدويًا! سنرى كيف يمكن استخدامها في القسم التالي.
## كيف يعمل داخليًا؟
قمنا بإنشاء غلاف رقيق فوق [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) والذي يجعله أكثر كفاءة ويتجنب عمليات إعادة التقديم غير الضرورية.
ونقوم أيضًا بإنشاء حالة Recoil للتعامل مع حالة نطاق المفتاح وجعلها متاحة في جميع أنحاء التطبيق.
ونقوم أيضًا بإنشاء ذرة Jotai للتعامل مع حالة نطاق مفاتيح الاختصار وجعلها متاحة في جميع أنحاء التطبيق.
@@ -13,7 +13,9 @@ info: تعرّف على كيفية التعاون باستخدام Figma الخ
تتوفر المميزات الرئيسية فقط للمستخدمين الذين قاموا بتسجيل الدخول، مثل وضع المطور والقدرة على اختيار إطار مخصص.
<Warning>
لن تتمكن من التعاون بفعالية بدون حساب.
لن تتمكن من التعاون بفعالية بدون حساب.
</Warning>
## هيكل فيجما
@@ -6,66 +6,66 @@ description: الدليل للمساهمين (أو المطورين الفضول
## المتطلبات الأساسية
<Tabs>
<Tab title="Linux و MacOS">
قبل أن تتمكن من تثبيت واستخدام Twenty، تأكد من تثبيت الأمور التالية على جهاز الكمبيوتر الخاص بك:
<Tab title="Linux و MacOS">
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
* [yarn v4](https://yarnpkg.com/getting-started/install)
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
قبل أن تتمكن من تثبيت واستخدام Twenty، تأكد من تثبيت الأمور التالية على جهاز الكمبيوتر الخاص بك:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
* [yarn v4](https://yarnpkg.com/getting-started/install)
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
<Warning>
لن يعمل `npm` ، يجب عليك استخدام `yarn` بدلًا من ذلك. يأتي Yarn الآن مع Node.js، لذا لست بحاجة إلى تثبيته بشكل منفصل.
عليك فقط تشغيل `corepack enable` لتفعيل Yarn إذا لم تقم بذلك بعد.
</Warning>
</Tab>
<Warning>
لن يعمل `npm` ، يجب عليك استخدام `yarn` بدلًا من ذلك. يأتي Yarn الآن مع Node.js، لذا لست بحاجة إلى تثبيته بشكل منفصل.
عليك فقط تشغيل `corepack enable` لتفعيل Yarn إذا لم تقم بذلك بعد.
</Warning>
<Tab title="ويندوز (WSL)">
1. ثبّت WSL
افتح PowerShell كمسؤول ثم نفّذ:
</Tab>
```powershell
wsl --install
```
<Tab title="ويندوز (WSL)">
يجب أن ترى الآن مطالبة لإعادة تشغيل جهاز الكمبيوتر الخاص بك. إذا لم يكن كذلك، فأعد تشغيله يدويًا.
1. ثبّت WSL
افتح PowerShell كمسؤول ثم نفّذ:
```powershell
wsl --install
```
يجب أن ترى الآن مطالبة لإعادة تشغيل جهاز الكمبيوتر الخاص بك. إذا لم يكن كذلك، فأعد تشغيله يدويًا.
عند إعادة التشغيل، ستُفتح نافذة PowerShell وسيتم تثبيت Ubuntu. قد يستغرق هذا وقتًا طويلاً.
سترى مطالبة لإنشاء اسم المستخدم وكلمة المرور لتثبيت Ubuntu الخاص بك.
عند إعادة التشغيل، ستُفتح نافذة PowerShell وسيتم تثبيت Ubuntu. قد يستغرق هذا وقتًا طويلاً.
سترى مطالبة لإنشاء اسم المستخدم وكلمة المرور لتثبيت Ubuntu الخاص بك.
2. تثبيت وإعداد git
2. تثبيت وإعداد git
```bash
sudo apt-get install git
```bash
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
```
git config --global user.email "youremail@domain.com"
```
3. تثبيت nvm و node.js و yarn
3. تثبيت nvm و node.js و yarn
<Warning>
استخدم `nvm` لتثبيت نسخة `node` الصحيحة. الملف `.nvmrc` يضمن استخدام جميع المشاركين لنفس النسخة.
</Warning>
<Warning>
استخدم `nvm` لتثبيت نسخة `node` الصحيحة. الملف `.nvmrc` يضمن استخدام جميع المشاركين لنفس النسخة.
</Warning>
```bash
sudo apt-get install curl
```bash
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
أغلق وأعد فتح برنامجك الطرفي لاستخدام nvm. ثم قم بتشغيل الأوامر التالية.
أغلق وأعد فتح برنامجك الطرفي لاستخدام nvm. ثم قم بتشغيل الأوامر التالية.
```bash
```bash
nvm install # يثبت إصدار node الموصى به
nvm install # يثبت إصدار node الموصى به
nvm use # استخدم إصدار node الموصى به
nvm use # استخدم إصدار node الموصى به
corepack enable
```
corepack enable
```
</Tab>
</Tab>
</Tabs>
---
@@ -75,19 +75,19 @@ description: الدليل للمساهمين (أو المطورين الفضول
في الطرفية الخاصة بك، قم بتشغيل الأمر التالي.
<Tabs>
<Tab title="SSH (موصى به)">
إذا لم تكن قد أعددت مفاتيح SSH بالفعل، يمكنك معرفة كيفية القيام بذلك [هنا](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
<Tab title="SSH (موصى به)">
إذا لم تكن قد أعددت مفاتيح SSH بالفعل، يمكنك معرفة كيفية القيام بذلك [هنا](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
```bash
git clone git@github.com:twentyhq/twenty.git
```
</Tab>
<Tab title="HTTPS">
```bash
git clone git@github.com:twentyhq/twenty.git
```
</Tab>
```bash
git clone https://github.com/twentyhq/twenty.git
```
<Tab title="HTTPS">
```bash
git clone https://github.com/twentyhq/twenty.git
```
</Tab>
</Tab>
</Tabs>
## الخطوة 2: انتقل إلى جذر المشروع
@@ -104,20 +104,16 @@ cd twenty
<Tab title="Linux">
**الخيار 1 (المفضل):** لتوفير قاعدة بياناتك محليًا:
استخدم الرابط التالي لتثبيت Postgresql على جهاز Linux الخاص بك: [تثبيت Postgresql](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
ملاحظة: قد تحتاج إلى إضافة `sudo -u postgres` إلى الأمر قبل `psql` لتجنب أخطاء الإذن.
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="نظام Mac OS">
**الخيار 1 (المفضل):** لتوفير قاعدة بياناتك محليًا مع `brew`:
@@ -129,7 +125,6 @@ cd twenty
```
يمكنك التحقق مما إذا كان خادم PostgreSQL يعمل بتنفيذ:
```bash
brew services list
```
@@ -138,7 +133,6 @@ cd twenty
عبر Homebrew على MacOS. بدلاً من ذلك، فإنه ينشئ دور PostgreSQL يطابق
اسم المستخدم الخاص بك في MacOS (مثل "john").
للتحقق وإنشاء المستخدم `postgres` إذا لزم الأمر، اتبع هذه الخطوات:
```bash
# قم بالاتصال بPostgreSQL
psql postgres
@@ -147,59 +141,48 @@ cd twenty
```
بمجرد أن تكون عند مطالبة psql (postgres=#)، قم بتشغيل:
```bash
# قائمة الأدوار الموجودة في PostgreSQL
\du
```
```bash
# قائمة الأدوار الموجودة في PostgreSQL
\du
```
سترى مخرجات مشابهة ل:
```bash
اسم الأدوار | الخصائص | عضو في
-----------+-------------+-----------
john | مشرف نظام | {}
```
```bash
اسم الأدوار | الخصائص | عضو في
-----------+-------------+-----------
john | مشرف نظام | {}
```
إذا لم ترَ دور `postgres` مدرجًا، انتقل إلى الخطوة التالية.
قم بإنشاء دور `postgres` يدويًا:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
يقوم هذا بإنشاء دور مشرف نظام باسم `postgres` مع إمكانية تسجيل الدخول.
```bash
اسم الدور | الخصائص | عضو في
-----------+-------------+-----------
postgres | مشرف نظام | {}
john | مشرف نظام | {}
```
```bash
اسم الدور | الخصائص | عضو في
-----------+-------------+-----------
postgres | مشرف نظام | {}
john | مشرف نظام | {}
```
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="ويندوز (WSL)">
يجب أن تُنفذ جميع الخطوات التالية في تيرمينال WSL (داخل جهازك الافتراضي)
**الخيار 1:** لتوفير قاعدة بيانات Postgresql الخاصة بك محليًا:
استخدم الرابط التالي لتثبيت Postgresql على جهاز Linux الافتراضي الخاص بك: [تثبيت Postgresql](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
ملاحظة: قد تحتاج إلى إضافة `sudo -u postgres` إلى الأمر قبل `psql` لتجنب أخطاء الإذن.
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
تشغيل Docker على WSL يضيف طبقة إضافية من التعقيد.
استخدم هذا الخيار فقط إذا كنت مرتاحًا مع الخطوات الإضافية المتضمنة، بما في ذلك تشغيل [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
```bash
make -C packages/twenty-docker postgres-on-docker
```
@@ -218,35 +201,28 @@ cd twenty
استخدم الرابط التالي لتثبيت Redis على جهاز Linux: [تثبيت Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Mac OS">
**الخيار 1 (المفضل):** لتوفير Redis الخاص بك محليًا مع `brew`:
```bash
brew install redis
```
ابدأ خادم redis الخاص بك:
`brew services start redis`
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="ويندوز (WSL)">
**الخيار 1:** لتوفير Redis الخاص بك محليًا:
استخدم الرابط التالي لتثبيت Redis على جهاز Linux الافتراضي الخاص بك: [تثبيت Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**الخيار 2:** إذا كنت قد قمت بتثبيت docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
@@ -267,7 +243,7 @@ cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**وضع تعدد مساحات العمل:** بشكل افتراضي، يعمل Twenty في وضع مساحة عمل واحدة حيث يمكن إنشاء مساحة عمل واحدة فقط. لتمكين دعم تعدد مساحات العمل (مفيد لاختبار الميزات المعتمدة على النطاقات الفرعية)، عيّن `IS_MULTIWORKSPACE_ENABLED=true` في ملف الخادم `.env`. راجع [وضع تعدد مساحات العمل](/l/ar/developers/self-host/capabilities/setup#multi-workspace-mode) للحصول على التفاصيل.
**وضع تعدد مساحات العمل:** بشكل افتراضي، يعمل Twenty في وضع مساحة عمل واحدة حيث يمكن إنشاء مساحة عمل واحدة فقط. لتمكين دعم تعدد مساحات العمل (مفيد لاختبار الميزات المعتمدة على النطاقات الفرعية)، عيّن `IS_MULTIWORKSPACE_ENABLED=true` في ملف الخادم `.env`. راجع [وضع تعدد مساحات العمل](/l/ar/developers/self-host/capabilities/setup#multi-workspace-mode) للحصول على التفاصيل.
</Info>
## الخطوة 6: تثبيت التبعيات
@@ -287,15 +263,12 @@ yarn
اعتمادًا على توزيعة Linux الخاصة بك، قد يتم بدء خادم Redis تلقائيًا.
إذا لم يكن كذلك، تحقق من [دليل تثبيت Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) لتوزيعتك.
</Tab>
<Tab title="نظام Mac OS">
من المفترض أن يكون Redis قد تم تشغيله بالفعل. إذا لم يكن كذلك، قم بتشغيل:
من المفترض أن يكون Redis قد تم تشغيله بالفعل. إذا لم يكن كذلك، قم بتشغيل:
```bash
brew services start redis
```
</Tab>
<Tab title="ويندوز (WSL)">
اعتمادًا على توزيعة Linux الخاصة بك، قد يتم بدء خادم Redis تلقائيًا.
إذا لم يكن كذلك، تحقق من [دليل تثبيت ريديس](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) لتوزيعتك.
@@ -25,7 +25,6 @@ Twenty مفتوح المصدر ويرحب بمساهمات المجتمع. سو
<Card title="تقارير الأخطاء والطلبات" icon="bug" href="/l/ar/developers/contribute/capabilities/bug-and-requests">
أبلغ عن المشكلات أو اطلب ميزات
</Card>
<Card title="تطوير الواجهة الأمامية" icon="browser" href="/l/ar/developers/contribute/capabilities/frontend-development">
ساهم في واجهة المستخدم
</Card>
@@ -17,7 +17,7 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
* **وثائق مخصصة**: يتم إنشاؤها خصيصًا لنموذج بيانات مساحة عملك
<Note>
وثائق واجهة برمجة التطبيقات المخصصة لك متاحة ضمن **الإعدادات → API & Webhooks** بعد إنشاء مفتاح API. نظرًا لأن Twenty تُنشئ واجهات برمجة تطبيقات تتطابق مع نموذج البيانات المخصص لديك، فإن الوثائق فريدة لمساحة عملك.
وثائق واجهة برمجة التطبيقات المخصصة لك متاحة ضمن **الإعدادات → API & Webhooks** بعد إنشاء مفتاح API. نظرًا لأن Twenty تُنشئ واجهات برمجة تطبيقات تتطابق مع نموذج البيانات المخصص لديك، فإن الوثائق فريدة لمساحة عملك.
</Note>
## نوعا واجهات برمجة التطبيقات
@@ -81,7 +81,7 @@ Authorization: Bearer YOUR_API_KEY
<VimeoEmbed videoId="928786722" title="إنشاء مفتاح API" />
<Warning>
يمنح مفتاح API الخاص بك الوصول إلى بيانات حساسة. لا تشاركه مع خدمات غير موثوقة. إذا تم اختراقه، عطّلْه فوراً وأنشئ مفتاحاً جديداً.
يمنح مفتاح API الخاص بك الوصول إلى بيانات حساسة. لا تشاركه مع خدمات غير موثوقة. إذا تم اختراقه، عطّلْه فوراً وأنشئ مفتاحاً جديداً.
</Warning>
### تعيين دور لمفتاح API
@@ -143,5 +143,5 @@ Authorization: Bearer YOUR_API_KEY
| **حجم الدفعة** | 60 سجل لكل استدعاء |
<Tip>
استخدم عمليات الدفعات لزيادة الإنتاجية — عالج ما يصل إلى 60 سجلًا في استدعاء API واحد بدلاً من إجراء طلبات فردية.
استخدم عمليات الدفعات لزيادة الإنتاجية — عالج ما يصل إلى 60 سجلًا في استدعاء API واحد بدلاً من إجراء طلبات فردية.
</Tip>
@@ -4,7 +4,7 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
---
<Warning>
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
## ما هي التطبيقات؟
@@ -32,13 +32,6 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# إذا كنت لا تستخدم yarn@4
corepack enable
yarn install
# قم بالمصادقة باستخدام مفتاح واجهة برمجة التطبيقات الخاص بك (سيُطلب منك ذلك)
yarn twenty auth:login
# ابدأ وضع التطوير: يُزامن التغييرات المحلية تلقائيًا مع مساحة العمل الخاصة بك
yarn twenty app:dev
```
@@ -59,22 +52,25 @@ npx create-twenty-app@latest my-app --interactive
من هنا يمكنك:
```bash filename="Terminal"
# Add a new entity to your application (guided)
# أضف كيانًا جديدًا إلى تطبيقك (موجّه)
yarn twenty entity:add
# Watch your application's function logs
# راقب سجلات وظائف تطبيقك
yarn twenty function:logs
# Execute a function by name
# نفّذ وظيفة بالاسم
yarn twenty function:execute -n my-function -p '{"name": "test"}'
# Execute the post-install function
# نفّذ دالة ما قبل التثبيت
yarn twenty function:execute --preInstall
# نفّذ دالة ما بعد التثبيت
yarn twenty function:execute --postInstall
# Uninstall the application from the current workspace
# أزل تثبيت التطبيق من مساحة العمل الحالية
yarn twenty app:uninstall
# Display commands' help
# اعرض مساعدة الأوامر
yarn twenty help
```
@@ -87,7 +83,7 @@ yarn twenty help
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالة ما بعد التثبيت) بالإضافة إلى ملفات الأمثلة بحسب وضع الإنشاء
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
@@ -114,6 +110,7 @@ my-twenty-app/
│ └── example-field.ts # تعريف حقل مستقل — مثال
├── logic-functions/
│ ├── hello-world.ts # دالة منطقية — مثال
│ ├── pre-install.ts # دالة منطقية لما قبل التثبيت
│ └── post-install.ts # دالة منطقية لما بعد التثبيت
├── front-components/
│ └── hello-world.tsx # مكوّن واجهة أمامية — مثال
@@ -125,7 +122,7 @@ my-twenty-app/
└── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال
```
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts` و`roles/default-role.ts` و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
بشكل عام:
@@ -142,19 +139,21 @@ my-twenty-app/
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
| دالة مساعدة | نوع الكيان |
| ---------------------------- | ------------------------------------ |
| `defineObject()` | تعريفات كائنات مخصصة |
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | تعريفات الأدوار |
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
| `defineView()` | تعريفات العروض المحفوظة |
| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل |
| `defineSkill()` | تعريفات مهارات وكيل الذكاء الاصطناعي |
| دالة مساعدة | نوع الكيان |
| ---------------------------------- | ---------------------------------------------- |
| `defineObject()` | تعريفات كائنات مخصصة |
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
| `definePreInstallLogicFunction()` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) |
| `definePostInstallLogicFunction()` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | تعريفات الأدوار |
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
| `defineView()` | تعريفات العروض المحفوظة |
| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل |
| `defineSkill()` | تعريفات مهارات وكيل الذكاء الاصطناعي |
<Note>
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
</Note>
مثال على كيان تم اكتشافه:
@@ -172,7 +171,7 @@ export default defineObject({
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
* `yarn twenty app:dev` سيولّد تلقائيًا عميل API مضبوط الأنواع في `node_modules/twenty-sdk/generated` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
* سيقوم `yarn twenty app:dev` بتوليد عميلين API مضبوطي الأنواع تلقائيًا في `node_modules/twenty-sdk/generated`: `CoreApiClient` (لبيانات مساحة العمل عبر `/graphql`) و`MetadataApiClient` (لتكوين مساحة العمل وتحميل الملفات عبر `/metadata`).
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك.
## المصادقة
@@ -216,17 +215,19 @@ yarn twenty auth:status
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
| دالة | الغرض |
| ---------------------------- | ---------------------------------------------------- |
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
| `defineView()` | تعريف العروض المحفوظة للكائنات |
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
| `defineSkill()` | عرّف مهارات وكيل الذكاء الاصطناعي |
| دالة | الغرض |
| ---------------------------------- | ---------------------------------------------------- |
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
| `definePreInstallLogicFunction()` | تعريف دالة منطقية لما قبل التثبيت (واحدة لكل تطبيق) |
| `definePostInstallLogicFunction()` | تعريف دالة منطقية لما بعد التثبيت (واحدة لكل تطبيق) |
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
| `defineView()` | تعريف العروض المحفوظة للكائنات |
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
| `defineSkill()` | عرّف مهارات وكيل الذكاء الاصطناعي |
تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
@@ -312,11 +313,11 @@ export default defineObject({
* يمكنك إنشاء كائنات جديدة باستخدام `yarn twenty entity:add`، والذي يرشدك خلال التسمية والحقول والعلاقات.
<Note>
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية
مثل `id` و`name` و`createdAt` و`updatedAt` و`createdBy` و`updatedBy` و`deletedAt`.
لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
يمكنك تجاوز الحقول الافتراضية من خلال تعريف حقل بالاسم نفسه في مصفوفة `fields` الخاصة بك،
لكن هذا غير مستحسن.
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية
مثل `id` و`name` و`createdAt` و`updatedAt` و`createdBy` و`updatedBy` و`deletedAt`.
لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
يمكنك تجاوز الحقول الافتراضية من خلال تعريف حقل بالاسم نفسه في مصفوفة `fields` الخاصة بك،
لكن هذا غير مستحسن.
</Note>
### تكوين التطبيق (application-config.ts)
@@ -326,6 +327,7 @@ export default defineObject({
* **هوية التطبيق**: المعرفات، اسم العرض، والوصف.
* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات.
* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
* **(اختياري) دالة ما قبل التثبيت**: دالة منطقية تعمل قبل تثبيت التطبيق.
* **(Optional) post-install function**: a logic function that runs after the app is installed.
Use `defineApplication()` to define your application configuration:
@@ -334,23 +336,21 @@ Use `defineApplication()` to define your application configuration:
// src/application-config.ts
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
displayName: 'My Twenty App',
description: 'My first Twenty app',
displayName: 'تطبيق Twenty الخاص بي',
description: 'أول تطبيق لي لـ Twenty',
icon: 'IconWorld',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
description: 'الاسم الافتراضي للمستلم للبطاقات البريدية',
value: 'Jane Doe',
isSecret: false,
},
},
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
@@ -359,7 +359,7 @@ export default defineApplication({
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه).
* `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions).
* يتم اكتشاف دوال ما قبل التثبيت وما بعد التثبيت تلقائيًا أثناء إنشاء ملف البيان. راجع [دوال ما قبل التثبيت](#pre-install-functions) و[دوال ما بعد التثبيت](#post-install-functions).
#### الأدوار والصلاحيات
@@ -433,10 +433,10 @@ export default defineRole({
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '~/generated';
import { CoreApiClient, type Person } from 'twenty-sdk/generated';
const handler = async (params: RoutePayload) => {
const client = new Twenty(); // generated typed client
const client = new CoreApiClient();
const name = 'name' in params.queryStringParameters
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
: 'Hello world';
@@ -498,6 +498,44 @@ export default defineLogicFunction({
* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى.
* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة.
### دوال ما قبل التثبيت
دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا قبل تثبيت تطبيقك على مساحة عمل. يفيد ذلك في مهام التحقق، وفحص المتطلبات المسبقة، أو تجهيز حالة مساحة العمل قبل متابعة التثبيت الرئيسي.
عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما قبل التثبيت لك في `src/logic-functions/pre-install.ts`:
```typescript
// src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Pre install logic function executed successfully!', payload.previousVersion);
};
export default definePreInstallLogicFunction({
universalIdentifier: '<generated-uuid>',
name: 'pre-install',
description: 'Runs before installation to prepare the application.',
timeoutSeconds: 300,
handler,
});
```
يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI:
```bash filename="Terminal"
yarn twenty function:execute --preInstall
```
النقاط الرئيسية:
* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`).
* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة).
* يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `preInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`.
* تم ضبط المهلة الافتراضية على 300 ثانية (5 دقائق) للسماح بمهام التحضير الأطول.
* لا تحتاج دوال ما قبل التثبيت إلى مُشغِّلات — إذ يستدعيها النظام الأساسي قبل التثبيت أو يدويًا عبر `function:execute --preInstall`.
### Post-install functions
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
@@ -506,16 +544,14 @@ A post-install function is a logic function that runs automatically after your a
```typescript
// src/logic-functions/post-install.ts
import { defineLogicFunction } from 'twenty-sdk';
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
const handler = async (): Promise<void> => {
console.log('Post install logic function executed successfully!');
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
console.log('Post install logic function executed successfully!', payload.previousVersion);
};
export default defineLogicFunction({
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
export default definePostInstallLogicFunction({
universalIdentifier: '<generated-uuid>',
name: 'post-install',
description: 'Runs after installation to set up the application.',
timeoutSeconds: 300,
@@ -523,17 +559,6 @@ export default defineLogicFunction({
});
```
يتم ربط الدالة بتطبيقك من خلال الإشارة إلى المعرِّف العالمي الخاص بها في `application-config.ts`:
```typescript
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
export default defineApplication({
// ...
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
});
```
يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI:
```bash filename="Terminal"
@@ -542,35 +567,35 @@ yarn twenty function:execute --postInstall
النقاط الرئيسية:
* دوال ما بعد التثبيت هي دوال منطقية قياسية — فهي تستخدم `defineLogicFunction()` مثل أي دالة أخرى.
* حقل `postInstallLogicFunctionUniversalIdentifier` في `defineApplication()` اختياري. إذا تم تجاهله، لن يتم تشغيل أي دالة بعد التثبيت.
* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`).
* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة).
* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `postInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`.
* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات.
* لا تحتاج دوال ما بعد التثبيت إلى مُشغِّلات — حيث يستدعيها النظام الأساسي أثناء التثبيت أو يدويًا عبر `function:execute --postInstall`.
### حمولة مشغل المسار
<Warning>
**تغيير غير متوافق (v1.16، يناير 2026):** لقد تغير تنسيق حمولة مشغل المسار. قبل v1.16، كانت معلمات الاستعلام، ومعلمات المسار، وجسم الطلب تُرسل مباشرةً كحمولة. بدءًا من v1.16، أصبحت متداخلة داخل كائن منظَّم `RoutePayload`.
**تغيير غير متوافق (v1.16، يناير 2026):** لقد تغير تنسيق حمولة مشغل المسار. قبل v1.16، كانت معلمات الاستعلام، ومعلمات المسار، وجسم الطلب تُرسل مباشرةً كحمولة. بدءًا من v1.16، أصبحت متداخلة داخل كائن منظَّم `RoutePayload`.
**قبل v1.16:**
**قبل v1.16:**
```typescript
const handler = async (params) => {
const { param1, param2 } = params; // Direct access
};
```
```typescript
const handler = async (params) => {
const { param1, param2 } = params; // Direct access
};
```
**بعد v1.16:**
```typescript
const handler = async (event: RoutePayload) => {
const { param1, param2 } = event.body; // Access via .body
const { queryParam } = event.queryStringParameters;
const { id } = event.pathParameters;
};
```
**بعد v1.16:**
```typescript
const handler = async (event: RoutePayload) => {
const { param1, param2 } = event.body; // Access via .body
const { queryParam } = event.queryStringParameters;
const { id } = event.pathParameters;
};
```
**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params.
**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params.
</Warning>
عندما يستدعي مشغّل المسار وظيفتك المنطقية، يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`:
@@ -653,10 +678,10 @@ const handler = async (event: RoutePayload) => {
```typescript
// src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import Twenty from '~/generated';
import { CoreApiClient } from 'twenty-sdk/generated';
const handler = async (params: { companyName: string; domain?: string }) => {
const client = new Twenty();
const client = new CoreApiClient();
const result = await client.mutation({
createTask: {
@@ -706,7 +731,7 @@ export default defineLogicFunction({
* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة أحداث (cron، وأحداث قاعدة البيانات، والمسارات) في الوقت نفسه.
<Note>
**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
</Note>
### المكوّنات الأمامية
@@ -714,7 +739,7 @@ export default defineLogicFunction({
تتيح لك المكوّنات الأمامية إنشاء مكوّنات React مخصّصة تُعرَض داخل واجهة مستخدم Twenty. استخدم `defineFrontComponent()` لتعريف مكوّنات مع تحقّق مدمج:
```typescript
// src/my-widget.front-component.tsx
// src/front-components/my-widget.tsx
import { defineFrontComponent } from 'twenty-sdk';
const MyWidget = () => {
@@ -737,14 +762,13 @@ export default defineFrontComponent({
النقاط الرئيسية:
* المكوّنات الأمامية هي مكوّنات React تُعرَض ضمن سياقات معزولة داخل Twenty.
* استخدم لاحقة الملف `*.front-component.tsx` للاكتشاف التلقائي.
* يشير الحقل `component` إلى مكوّن React الخاص بك.
* يتم بناء المكوّنات ومزامنتها تلقائيًا أثناء `yarn twenty app:dev`.
يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين:
* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة مكوّن أمامي جديد.
* **يدوي**: أنشئ ملفًا جديدًا `*.front-component.tsx` واستخدم `defineFrontComponent()`.
* **يدوي**: أنشئ ملفًا جديدًا `.tsx` واستخدم `defineFrontComponent()` مع اتباع النمط نفسه.
### المهارات
@@ -781,18 +805,24 @@ export default defineSkill({
* **مُنشأ بالقالب**: شغِّل `yarn twenty entity:add` واختر خيار إضافة مهارة جديدة.
* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineSkill()` مع اتباع النمط نفسه.
### عميل مُولَّد مضبوط الأنواع
### عملاء مُولَّدون مضبوطو الأنواع
يُولَّد العميل مضبوط الأنواع تلقائيًا بواسطة `yarn twenty app:dev` ويُخزَّن في `node_modules/twenty-sdk/generated` استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
يتم توليد عميلين مضبوطي الأنواع تلقائيًا بواسطة `yarn twenty app:dev` وتخزينهما في `node_modules/twenty-sdk/generated` استنادًا إلى مخطط مساحة العمل لديك:
* **`CoreApiClient`** — يُجري استعلامات إلى نقطة النهاية `/graphql` للحصول على بيانات مساحة العمل
* **`MetadataApiClient`** — يستعلم عن نقطة النهاية `/metadata` لتكوين مساحة العمل وتحميل الملفات.
```typescript
import Twenty from '~/generated';
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
const client = new Twenty();
const client = new CoreApiClient();
const { me } = await client.query({ me: { id: true, displayName: true } });
const metadataClient = new MetadataApiClient();
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
```
يُعاد توليد العميل تلقائيًا بواسطة `yarn twenty app:dev` كلما تغيّرت كائناتك أو حقولك.
يُعاد توليد كلا العميلين تلقائيًا بواسطة `yarn twenty app:dev` كلما تغيّرت كائناتك أو حقولك.
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
@@ -809,17 +839,17 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
#### رفع الملفات
يتضمن العميل `Twenty` المُولَّد طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
يتضمن `MetadataApiClient` المُولَّد طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
```typescript
import Twenty from '~/generated';
import { MetadataApiClient } from 'twenty-sdk/generated';
import * as fs from 'fs';
const client = new Twenty();
const metadataClient = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
const uploadedFile = await client.uploadFile(
const uploadedFile = await metadataClient.uploadFile(
fileBuffer, // file contents as a Buffer
'invoice.pdf', // filename
'application/pdf', // MIME type (defaults to 'application/octet-stream')
@@ -850,7 +880,7 @@ uploadFile(
النقاط الرئيسية:
* ترسل هذه الطريقة الملف إلى **نقطة نهاية البيانات الوصفية** (وليست نقطة النهاية الرئيسية لـ GraphQL)، حيث تُنفَّذ عملية الرفع.
* تتوفر طريقة `uploadFile` على `MetadataApiClient` لأن عملية الـ mutation الخاصة بالرفع تُعالَج عبر نقطة النهاية `/metadata`.
* تستخدم `universalIdentifier` الخاص بالحقل (وليس المعرّف الخاص بمساحة العمل)، ليعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك — بما يتماشى مع كيفية إشارة التطبيقات إلى الحقول في كل مكان آخر.
* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع.
@@ -62,7 +62,7 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
| `الطابع الزمني` | وقت حدوث الحدث (UTC) |
<Note>
استجب بحالة **HTTP 2xx** (200-299) لتأكيد الاستلام. تُسجَّل الاستجابات غير 2xx كإخفاقات في التسليم.
استجب بحالة **HTTP 2xx** (200-299) لتأكيد الاستلام. تُسجَّل الاستجابات غير 2xx كإخفاقات في التسليم.
</Note>
## التحقق من صحة خطاف الويب
@@ -23,11 +23,9 @@ description: وسّع وظائف Twenty باستخدام واجهات برمجة
<Card title="واجهات برمجة التطبيقات" icon="كود" href="/l/ar/developers/extend/capabilities/apis">
اتصل بـ Twenty برمجياً
</Card>
<Card title="الويب هوكس" icon="bell" href="/l/ar/developers/extend/capabilities/webhooks">
احصل على إشعارات بالأحداث في الوقت الفعلي
</Card>
<Card title="التطبيقات" icon="puzzle-piece" href="/l/ar/developers/extend/capabilities/apps">
أنشئ تخصيصات كرمز برمجي (ألفا)
</Card>
@@ -3,7 +3,7 @@ title: بنقرة واحدة مع Docker Compose
---
<Warning>
الحاويات الخاصة بدوكر مخصصة للاستضافة الإنتاجية أو الاستضافة الذاتية، للتحقيق يرجى التحقق من [الإعداد المحلي](/l/ar/developers/contribute/capabilities/local-setup).
الحاويات الخاصة بدوكر مخصصة للاستضافة الإنتاجية أو الاستضافة الذاتية، للتحقيق يرجى التحقق من [الإعداد المحلي](/l/ar/developers/contribute/capabilities/local-setup).
</Warning>
## نظرة عامة
@@ -5,7 +5,7 @@ title: إعداد
# إدارة الإعدادات
<Warning>
**هل هي المرة الأولى التي تقوم فيها بالتثبيت؟** اتبع [دليل تثبيت Docker Compose](/l/ar/developers/self-host/capabilities/docker-compose) لتشغيل Twenty، ثم عد هنا للإعداد.
**هل هي المرة الأولى التي تقوم فيها بالتثبيت؟** اتبع [دليل تثبيت Docker Compose](/l/ar/developers/self-host/capabilities/docker-compose) لتشغيل Twenty، ثم عد هنا للإعداد.
</Warning>
يوفر Twenty **وضعين للإعداد** ليلائم احتياجات النشر المختلفة:
@@ -26,7 +26,7 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # افتراضي
4. تسري التغييرات على الفور (خلال 15 ثانية لعمليات النشر متعددة الحاويات)
<Warning>
**نشرات متعددة الحاويات:** عند استخدام إعدادات قاعدة البيانات (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`)، يقوم كل من حاويات الخادم والعامل بالقراءة من نفس قاعدة البيانات. التغييرات في لوحة الإدارة تؤثر عليهما تلقائيًا، مما يلغي الحاجة إلى تكرار متغيرات البيئة بين الحاويات (باستثناء متغيرات البنية التحتية).
**نشرات متعددة الحاويات:** عند استخدام إعدادات قاعدة البيانات (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`)، يقوم كل من حاويات الخادم والعامل بالقراءة من نفس قاعدة البيانات. التغييرات في لوحة الإدارة تؤثر عليهما تلقائيًا، مما يلغي الحاجة إلى تكرار متغيرات البيئة بين الحاويات (باستثناء متغيرات البنية التحتية).
</Warning>
**ما يمكنك تكوينه عبر لوحة الإدارة:**
@@ -41,10 +41,10 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # افتراضي
![متغيرات تكوين لوحة الإدارة](/images/user-guide/setup/admin-panel-config-variables.png)
<Warning>
كل متغير موثق بوصف في لوحة الإدارة الخاصة بك في **الإعدادات → لوحة الإدارة → متغيرات التكوين**.
بعض إعدادات البنية التحتية مثل اتصالات قاعدة البيانات (`PG_DATABASE_URL`)، عناوين الخوادم (`SERVER_URL`)، وأسرار التطبيقات (`APP_SECRET`) يمكن ضبطها فقط عبر ملف `.env`.
كل متغير موثق بوصف في لوحة الإدارة الخاصة بك في **الإعدادات → لوحة الإدارة → متغيرات التكوين**.
بعض إعدادات البنية التحتية مثل اتصالات قاعدة البيانات (`PG_DATABASE_URL`)، عناوين الخوادم (`SERVER_URL`)، وأسرار التطبيقات (`APP_SECRET`) يمكن ضبطها فقط عبر ملف `.env`.
[مرجع تقني كامل →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
[مرجع تقني كامل →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## 2. إعداد بيئي فقط
@@ -93,7 +93,7 @@ DEFAULT_SUBDOMAIN=app # default value
* إعدادات خاصة بمساحة العمل مثل النطاق الفرعي والنطاق المخصص تصبح متاحة ضمن إعدادات مساحة العمل
<Warning>
**إعداد خاص بالبيئة فقط:** لا يمكن تكوين `IS_MULTIWORKSPACE_ENABLED` إلا عبر ملف `.env` ويتطلب إعادة تشغيل. لا يمكن تغييره عبر لوحة الإدارة.
**إعداد خاص بالبيئة فقط:** لا يمكن تكوين `IS_MULTIWORKSPACE_ENABLED` إلا عبر ملف `.env` ويتطلب إعادة تشغيل. لا يمكن تغييره عبر لوحة الإدارة.
</Warning>
### تكوين DNS لوضع تعدد مساحات العمل
@@ -149,7 +149,7 @@ IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
* `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token`
<Warning>
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
</Warning>
**النطاقات المطلوبة** (يتم تكوينها تلقائيًا):
@@ -168,7 +168,7 @@ IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
## تكامل Microsoft 365
<Warning>
يجب على المستخدمين الحصول على [ترخيص Microsoft 365](https://admin.microsoft.com/Adminportal/Home) ليتمكنوا من استخدام تقويم API ورسائل. لن يتمكنوا من مزامنة حسابهم في Twenty دون واحد منها.
يجب على المستخدمين الحصول على [ترخيص Microsoft 365](https://admin.microsoft.com/Adminportal/Home) ليتمكنوا من استخدام تقويم API ورسائل. لن يتمكنوا من مزامنة حسابهم في Twenty دون واحد منها.
</Warning>
### إنشاء مشروع في Microsoft Azure
@@ -211,7 +211,7 @@ IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
* `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token`
<Warning>
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
</Warning>
### تكوين النطاقات
@@ -256,40 +256,45 @@ yarn command:prod cron:workflow:automated-cron-trigger
3. قم بضبط إعدادات SMTP الخاصة بك:
<ArticleTabs label1="جيميل" label2="أوفيس 365" label3="Smtp4dev">
<ArticleTab>
ستحتاج إلى توفير [كلمة مرور التطبيق](https://support.google.com/accounts/answer/185833).
<ArticleTab>
ستحتاج إلى توفير [كلمة مرور التطبيق](https://support.google.com/accounts/answer/185833).
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.gmail.com
* EMAIL_SMTP_PORT=465
* EMAIL_SMTP_USER=gmail_email_address
* EMAIL_SMTP_PASSWORD='gmail_app_password'
</ArticleTab>
<ArticleTab>
تذكر أنه إذا كنت تشغل التحقق بعاملين، ستحتاج إلى توفير [كلمة مرور التطبيق](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9).
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.office365.com
* EMAIL_SMTP_PORT=587
* EMAIL_SMTP_USER=office365_email_address
* EMAIL_SMTP_PASSWORD='office365_password'
</ArticleTab>
<ArticleTab>
**smtp4dev** هو خادم بريد إلكتروني مزيف للتطوير والاختبار.
* قم بتشغيل صورة smtp4dev: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
* الوصول إلى واجهة المستخدم smtp4dev هنا: [http://localhost:8090](http://localhost:8090)
* حدد المتغيرات التالية:
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.gmail.com
* EMAIL_SMTP_PORT=465
* EMAIL_SMTP_USER=gmail_email_address
* EMAIL_SMTP_PASSWORD='gmail_app_password'
* EMAIL_SMTP_HOST=localhost
* EMAIL_SMTP_PORT=2525
</ArticleTab>
<ArticleTab>
تذكر أنه إذا كنت تشغل التحقق بعاملين، ستحتاج إلى توفير [كلمة مرور التطبيق](https://support.microsoft.com/en-us/account-billing/manage-app-passwords-for-two-step-verification-d6dc8c6d-4bf7-4851-ad95-6d07799387e9).
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.office365.com
* EMAIL_SMTP_PORT=587
* EMAIL_SMTP_USER=office365_email_address
* EMAIL_SMTP_PASSWORD='office365_password'
</ArticleTab>
<ArticleTab>
**smtp4dev** هو خادم بريد إلكتروني مزيف للتطوير والاختبار.
* قم بتشغيل صورة smtp4dev: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
* الوصول إلى واجهة المستخدم smtp4dev هنا: [http://localhost:8090](http://localhost:8090)
* حدد المتغيرات التالية:
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=localhost
* EMAIL_SMTP_PORT=2525
</ArticleTab>
</ArticleTabs>
<Warning>
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
**وضع بيئي فقط:** إذا كنت قد ضبطت `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`، فأضف هذه المتغيرات إلى ملف `.env` الخاص بك بدلاً من ذلك.
</Warning>
## الوظائف المنطقية
@@ -297,7 +302,7 @@ yarn command:prod cron:workflow:automated-cron-trigger
تدعم Twenty الوظائف المنطقية لعمليات سير العمل والمنطق المخصص. يتم تكوين بيئة التنفيذ عبر متغير البيئة `SERVERLESS_TYPE`.
<Warning>
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي (`SERVERLESS_TYPE=LOCAL`) بتشغيل الشيفرة مباشرةً على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. بالنسبة لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوق بها، نوصي بشدة باستخدام `SERVERLESS_TYPE=LAMBDA` أو `SERVERLESS_TYPE=DISABLED`.
**ملاحظة أمنية:** يقوم برنامج التشغيل المحلي (`SERVERLESS_TYPE=LOCAL`) بتشغيل الشيفرة مباشرةً على المضيف ضمن عملية Node.js من دون عزل. يجب استخدامه فقط للشيفرة الموثوقة أثناء التطوير. بالنسبة لعمليات النشر الإنتاجية التي تتعامل مع شيفرة غير موثوق بها، نوصي بشدة باستخدام `SERVERLESS_TYPE=LAMBDA` أو `SERVERLESS_TYPE=DISABLED`.
</Warning>
### برامج التشغيل المتاحة
@@ -333,5 +338,5 @@ SERVERLESS_TYPE=DISABLED
```
<Note>
عند استخدام `SERVERLESS_TYPE=DISABLED`، ستؤدي أي محاولة لتنفيذ وظيفة منطقية إلى إرجاع خطأ. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون إمكانات الوظائف المنطقية.
عند استخدام `SERVERLESS_TYPE=DISABLED`، ستؤدي أي محاولة لتنفيذ وظيفة منطقية إلى إرجاع خطأ. يكون هذا مفيدًا إذا كنت ترغب في تشغيل Twenty من دون إمكانات الوظائف المنطقية.
</Note>
@@ -23,7 +23,6 @@ description: قم بنشر Twenty وإدارته على البنية التحت
<Card title="Docker Compose" icon="docker" href="/l/ar/developers/self-host/capabilities/docker-compose">
إعداد سريع باستخدام Docker
</Card>
<Card title="مزودو الخدمات السحابية" icon="cloud" href="/l/ar/developers/self-host/capabilities/cloud-providers">
انشر على AWS أو GCP أو Azure
</Card>
@@ -10,46 +10,54 @@ image: /images/user-guide/tips/light-bulb.png
رسالة مختصرة تعرض معلومات إضافية عند تفاعل المستخدم مع عنصر.
<Tabs>
<Tab title="استخدام">
```jsx
import { AppTooltip } from "@/ui/display/tooltip/AppTooltip";
<Tab title="استخدام">
export const MyComponent = () => {
return (
<>
<p id="hoverText" style={{ display: "inline-block" }}>
رؤى العملاء
</p>
<AppTooltip
className
anchorSelect="#hoverText"
content="استكشاف سلوك العملاء وتفضيلاتهم"
delayHide={0}
offset={6}
noArrow={false}
isOpen={true}
place="bottom"
positionStrategy="absolute"
/>
</>
);
};
```
</Tab>
```jsx
import { AppTooltip } from "@/ui/display/tooltip/AppTooltip";
export const MyComponent = () => {
return (
<>
<p id="hoverText" style={{ display: "inline-block" }}>
رؤى العملاء
</p>
<AppTooltip
className
anchorSelect="#hoverText"
content="استكشاف سلوك العملاء وتفضيلاتهم"
delayHide={0}
offset={6}
noArrow={false}
isOpen={true}
place="bottom"
positionStrategy="absolute"
/>
</>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| اسم الفئة | نص | فئة CSS اختيارية للتنسيق الإضافي |
| اختيار الربط | محدد CSS | المحدِّد لمرساة التلميح (العنصر الذي يُفعّل التلميح) |
| المحتوى | نص | المحتوى الذي تريد عرضه داخل التلميح |
| تأخير الإخفاء | رقم | التأخير بالثواني قبل إخفاء التلميح بعد مغادرة المؤشر للمرساة |
| الإزاحة | رقم | الإزاحة بالبكسل لتحديد موضع التلميح |
| بدون سهم | قيمة منطقية | إذا كانت القيمة `صحيح`, سيتم إخفاء السهم في المربط التنبيهي |
| مفتوح | قيمة منطقية | إذا كانت القيمة `صحيح`, يكون المربط التنبيهي مفتوحًا افتراضيًا |
| المكان | نص `PlacesType` من `react-tooltip` | يحدد موضع المربط التنبيهي. تتضمن القيم `bottom`، `left`، `right`، `top`، `top-start`، `top-end`، `right-start`، `right-end`، `bottom-start`، `bottom-end`، `left-start`، و`left-end` |
| استراتيجية الوضعية | نص `PositionStrategy` من `react-tooltip` | استراتيجية وضعية للمربط التنبيهي. له قيمتان: `absolute` و`fixed` |
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| اسم الفئة | نص | فئة CSS اختيارية للتنسيق الإضافي |
| اختيار الربط | محدد CSS | المحدِّد لمرساة التلميح (العنصر الذي يُفعّل التلميح) |
| المحتوى | نص | المحتوى الذي تريد عرضه داخل التلميح |
| تأخير الإخفاء | رقم | التأخير بالثواني قبل إخفاء التلميح بعد مغادرة المؤشر للمرساة |
| الإزاحة | رقم | الإزاحة بالبكسل لتحديد موضع التلميح |
| بدون سهم | قيمة منطقية | إذا كانت القيمة `صحيح`, سيتم إخفاء السهم في المربط التنبيهي |
| مفتوح | قيمة منطقية | إذا كانت القيمة `صحيح`, يكون المربط التنبيهي مفتوحًا افتراضيًا |
| المكان | نص `PlacesType` من `react-tooltip` | يحدد موضع المربط التنبيهي. تتضمن القيم `bottom`، `left`، `right`، `top`، `top-start`، `top-end`، `right-start`، `right-end`، `bottom-start`، `bottom-end`، `left-start`، و`left-end` |
| استراتيجية الوضعية | نص `PositionStrategy` من `react-tooltip` | استراتيجية وضعية للمربط التنبيهي. له قيمتان: `absolute` و`fixed` |
</Tab>
</Tabs>
## نص متجاوز مع تلميح
@@ -57,22 +65,30 @@ image: /images/user-guide/tips/light-bulb.png
يعالج النص الزائد ويعرض مربط تنبيهي عند فيضان النص.
<Tabs>
<Tab title="استخدام">
```jsx
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
<Tab title="استخدام">
export const MyComponent = () => {
const crmTaskDescription =
'المتابعة مع العميل بشأن استفساره الأخير عن المنتج. مناقشة خيارات التسعير، ومعالجة أي مخاوف، وتقديم معلومات إضافية عن المنتج. تسجيل تفاصيل المحادثة في نظام إدارة علاقات العملاء (CRM) للرجوع إليها لاحقاً.';
```jsx
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
return <OverflowingTextWithTooltip text={crmTaskDescription} />;
};
```
</Tab>
export const MyComponent = () => {
const crmTaskDescription =
'المتابعة مع العميل بشأن استفساره الأخير عن المنتج. مناقشة خيارات التسعير، ومعالجة أي مخاوف، وتقديم معلومات إضافية عن المنتج. تسجيل تفاصيل المحادثة في نظام إدارة علاقات العملاء (CRM) للرجوع إليها لاحقاً.';
return <OverflowingTextWithTooltip text={crmTaskDescription} />;
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------- | ------ | --------------------------------------------- |
| نص | string | المحتوى الذي تريد عرضه في منطقة النص المتجاوز |
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------- | ------ | --------------------------------------------- |
| نص | string | المحتوى الذي تريد عرضه في منطقة النص المتجاوز |
</Tab>
</Tabs>
@@ -10,19 +10,25 @@ image: /images/user-guide/tasks/tasks_header.png
يمثل إجراءً ناجحًا أو مكتملًا.
<Tabs>
<Tab title="استخدام">
```jsx
import { Checkmark } from 'twenty-ui/display';
<Tab title="استخدام">
export const MyComponent = () => {
return <Checkmark />;
};
```
</Tab>
```jsx
import { Checkmark } from 'twenty-ui/display';
export const MyComponent = () => {
return <Checkmark />;
};
```
</Tab>
<Tab title="المحددات">
يمتد `React.ComponentPropsWithoutRef<'div'>` و يقبل جميع خصائص عنصر `div` العادي.
</Tab>
<Tab title="المحددات">
يمتد `React.ComponentPropsWithoutRef<'div'>` و يقبل جميع خصائص عنصر `div` العادي.
</Tab>
</Tabs>
## علامة صحيح متحركة
@@ -30,29 +36,38 @@ image: /images/user-guide/tasks/tasks_header.png
يمثل رمز علامة صحيح مع ميزة الإضافة للحركة.
<Tabs>
<Tab title="استخدام">
```jsx
import { AnimatedCheckmark } from 'twenty-ui/display';
export const MyComponent = () => {
return (
<AnimatedCheckmark
isAnimating={true}
color="green"
duration={0.5}
size={30}
/>
);
};
```
</Tab>
<Tab title="استخدام">
```jsx
import { AnimatedCheckmark } from 'twenty-ui/display';
export const MyComponent = () => {
return (
<AnimatedCheckmark
isAnimating={true}
color="green"
duration={0.5}
size={30}
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ----------- | ------------------------------------- | ----------------- |
| isAnimating | قيمة منطقية | يتحكم فيما إذا كانت علامة صحيح متحركة | خاطئ |
| اللون | string | لون علامة الاختيار | |
| المدة | رقم | مدة الحركة بالثواني | 0.5 ثانية |
| الحجم | رقم | حجم علامة الاختيار | 28 بكسل |
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ----------- | ------------------------------------- | ----------------- |
| isAnimating | قيمة منطقية | يتحكم فيما إذا كانت علامة صحيح متحركة | خاطئ |
| اللون | string | لون علامة الاختيار | |
| المدة | رقم | مدة الحركة بالثواني | 0.5 ثانية |
| الحجم | رقم | حجم علامة الاختيار | 28 بكسل |
</Tab>
</Tabs>
@@ -10,40 +10,48 @@ image: /images/user-guide/github/github-header.png
عنصر مرئي يمكن استخدامه كحاوية قابلة للنقر أو غير قابلة للنقر، مع علامة وعناصر اختيارية يسار ويمين، وخيارات تصميم متنوعة لعرض العلامات والبطاقات.
<Tabs>
<Tab title="استخدام">
```jsx
import { Chip } from 'twenty-ui/components';
export const MyComponent = () => {
return (
<Chip
size="large"
label="Clickable Chip"
clickable={true}
variant="highlighted"
accent="text-primary"
leftComponent
rightComponent
maxWidth="200px"
className
/>
);
};
<Tab title="استخدام">
```
</Tab>
```jsx
import { Chip } from 'twenty-ui/components';
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------ | ----------------------- | ---------------------------------------------------------------------- |
| linkToEntity | نص | الرابط إلى الكيان |
| معرف الكيان | نص | المعرف الفريد للكيان |
| الاسم | نص | اسم الكيان |
| رابط الصورة | نص | s picture", |
| نوع الصورة الرمزية | نوع الصورة الرمزية | نوع الصورة الرمزية التي تريد عرضها. لديه خياران: `مستدير` و `مربع` |
| التنوع | تعداد EntityChipVariant | تنوع الرقاقة الكيانية التي ترغب في عرضها. لديه خياران: `عادي` و `شفاف` |
| الأيقونة اليسرى | مكون رمز | مكون React يمثل رمزًا. يظهر على الجانب الأيسر من الرقاقة |
</Tab>
export const MyComponent = () => {
return (
<Chip
size="large"
label="Clickable Chip"
clickable={true}
variant="highlighted"
accent="text-primary"
leftComponent
rightComponent
maxWidth="200px"
className
/>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------ | ----------------------- | ---------------------------------------------------------------------- |
| linkToEntity | نص | الرابط إلى الكيان |
| معرف الكيان | نص | المعرف الفريد للكيان |
| الاسم | نص | اسم الكيان |
| رابط الصورة | نص | s picture", |
| نوع الصورة الرمزية | نوع الصورة الرمزية | نوع الصورة الرمزية التي تريد عرضها. لديه خياران: `مستدير` و `مربع` |
| التنوع | تعداد EntityChipVariant | تنوع الرقاقة الكيانية التي ترغب في عرضها. لديه خياران: `عادي` و `شفاف` |
| الأيقونة اليسرى | مكون رمز | مكون React يمثل رمزًا. يظهر على الجانب الأيسر من الرقاقة |
</Tab>
</Tabs>
## الأمثلة
@@ -100,39 +108,47 @@ export const MyComponent = () => {
عنصر يشبه الرقاقة لعرض معلومات عن كيان.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { BrowserRouter as Router } from 'react-router-dom';
import { IconTwentyStar } from 'twenty-ui/display';
import { Chip } from 'twenty-ui/components';
export const MyComponent = () => {
return (
<Router>
<Chip
linkToEntity="/entity-link"
entityId="entityTest"
name="Entity name"
pictureUrl=""
avatarType="rounded"
variant="regular"
LeftIcon={IconTwentyStar}
/>
</Router>
);
};
```
</Tab>
<Tab title="الاستخدام">
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------------ | ----------------------- | ---------------------------------------------------------------------- |
| linkToEntity | نص | الرابط إلى الكيان |
| معرف الكيان | نص | المعرف الفريد للكيان |
| الاسم | نص | اسم الكيان |
| رابط الصورة | نص | s picture", |
| نوع الصورة الرمزية | نوع الصورة الرمزية | نوع الصورة الرمزية التي تريد عرضها. لديه خياران: `مستدير` و `مربع` |
| التنوع | تعداد EntityChipVariant | تنوع الرقاقة الكيانية التي ترغب في عرضها. لديه خياران: `عادي` و `شفاف` |
| الأيقونة اليسرى | مكون رمز | مكون React يمثل رمزًا. يظهر على الجانب الأيسر من الرقاقة |
</Tab>
```jsx
import { BrowserRouter as Router } from 'react-router-dom';
import { IconTwentyStar } from 'twenty-ui/display';
import { Chip } from 'twenty-ui/components';
export const MyComponent = () => {
return (
<Router>
<Chip
linkToEntity="/entity-link"
entityId="entityTest"
name="Entity name"
pictureUrl=""
avatarType="rounded"
variant="regular"
LeftIcon={IconTwentyStar}
/>
</Router>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------------ | ----------------------- | ---------------------------------------------------------------------- |
| linkToEntity | نص | الرابط إلى الكيان |
| معرف الكيان | نص | المعرف الفريد للكيان |
| الاسم | نص | اسم الكيان |
| رابط الصورة | نص | s picture", |
| نوع الصورة الرمزية | نوع الصورة الرمزية | نوع الصورة الرمزية التي تريد عرضها. لديه خياران: `مستدير` و `مربع` |
| التنوع | تعداد EntityChipVariant | تنوع الرقاقة الكيانية التي ترغب في عرضها. لديه خياران: `عادي` و `شفاف` |
| الأيقونة اليسرى | مكون رمز | مكون React يمثل رمزًا. يظهر على الجانب الأيسر من الرقاقة |
</Tab>
</Tabs>
@@ -14,35 +14,44 @@ image: /images/user-guide/objects/objects.png
نستخدم أيقونات Tabler لـ React في جميع أنحاء التطبيق.
<Tabs>
<Tab title="التثبيت">
<br />
```
yarn add @tabler/icons-react
```
</Tab>
<Tab title="التثبيت">
<br />
<Tab title="الإزاحة">
يمكنك استيراد كل أيقونة كمكون. إليك مثال:
```
yarn add @tabler/icons-react
```
<br />
</Tab>
```jsx
import { IconArrowLeft } from "@tabler/icons-react";
<Tab title="الإزاحة">
export const MyComponent = () => {
return <IconArrowLeft color="red" size={48} />;
};
```
</Tab>
يمكنك استيراد كل أيقونة كمكون. إليك مثال:
<br />
```jsx
import { IconArrowLeft } from "@tabler/icons-react";
export const MyComponent = () => {
return <IconArrowLeft color="red" size={48} />;
};
```
</Tab>
<Tab title="الإزاحة">
| الإزاحة | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ------ | -------------------------------- | ----------------- |
| الحجم | رقم | ارتفاع وعرض الأيقونة بالبكسل | 24 |
| اللون | string | لون الأيقونات | اللون الحالي |
| الخط العريض | رقم | عرض الخط العريض للأيقونة بالبكسل | 2 |
</Tab>
<Tab title="الإزاحة">
| الإزاحة | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ------ | -------------------------------- | ----------------- |
| الحجم | رقم | ارتفاع وعرض الأيقونة بالبكسل | 24 |
| اللون | string | لون الأيقونات | اللون الحالي |
| الخط العريض | رقم | عرض الخط العريض للأيقونة بالبكسل | 2 |
</Tab>
</Tabs>
## أيقونات مخصصة
@@ -54,20 +63,29 @@ image: /images/user-guide/objects/objects.png
يعرض أيقونة دفتر العناوين.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { IconAddressBook } from 'twenty-ui/display';
export const MyComponent = () => {
return <IconAddressBook size={24} stroke={2} />;
};
```
</Tab>
<Tab title="الاستخدام">
```jsx
import { IconAddressBook } from 'twenty-ui/display';
export const MyComponent = () => {
return <IconAddressBook size={24} stroke={2} />;
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ----- | -------------------------------- | ----------------- |
| الحجم | رقم | ارتفاع وعرض الأيقونة بالبكسل | 24 |
| الخط العريض | رقم | عرض الخط العريض للأيقونة بالبكسل | 2 |
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ----- | -------------------------------- | ----------------- |
| الحجم | رقم | ارتفاع وعرض الأيقونة بالبكسل | 24 |
| الخط العريض | رقم | عرض الخط العريض للأيقونة بالبكسل | 2 |
</Tab>
</Tabs>
@@ -10,29 +10,38 @@ image: /images/user-guide/table-views/table.png
مكوّن لتصنيف المحتوى أو وسمه بصريًا.
<Tabs>
<Tab title="استخدام">
```jsx
import { Tag } from "@/ui/display/tag/components/Tag";
export const MyComponent = () => {
return (
<Tag
className
color="red"
text="Urgent"
onClick={() => console.log("click")}
/>
);
};
```
</Tab>
<Tab title="استخدام">
```jsx
import { Tag } from "@/ui/display/tag/components/Tag";
export const MyComponent = () => {
return (
<Tag
className
color="red"
text="Urgent"
onClick={() => console.log("click")}
/>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| --------- | ----- | --------------------------------------------------------------------------------------------------------------------- |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
| اللون | نص | لون العلامة. الخيارات تشمل: `أخضر`, `تركواز`, `سماوي`, `أزرق`, `أرجواني`, `وردي`, `أحمر`, `برتقالي`, `أصفر`, `رمادي`. |
| نص | نص | محتوى العلامة |
| عند_النقر | دالة | دالة اختيارية تُستدعى عند نقر المستخدم على العلامة |
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| --------- | ----- | --------------------------------------------------------------------------------------------------------------------- |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
| اللون | نص | لون العلامة. الخيارات تشمل: `أخضر`, `تركواز`, `سماوي`, `أزرق`, `أرجواني`, `وردي`, `أحمر`, `برتقالي`, `أصفر`, `رمادي`. |
| نص | نص | محتوى العلامة |
| عند_النقر | دالة | دالة اختيارية تُستدعى عند نقر المستخدم على العلامة |
</Tab>
</Tabs>
@@ -10,22 +10,28 @@ image: /images/user-guide/api/api.png
يستخدم محرر نصوص غني يعتمد على الكتل من [BlockNote](https://www.blocknotejs.org/) للسماح للمستخدمين بتحرير وعرض كتل المحتوى.
<Tabs>
<Tab title="استخدام">
```jsx
import { useBlockNote } from "@blocknote/react";
import { BlockEditor } from "@/ui/input/editor/components/BlockEditor";
<Tab title="استخدام">
export const MyComponent = () => {
const BlockNoteEditor = useBlockNote();
```jsx
import { useBlockNote } from "@blocknote/react";
import { BlockEditor } from "@/ui/input/editor/components/BlockEditor";
return <BlockEditor editor={BlockNoteEditor} />;
};
```
</Tab>
export const MyComponent = () => {
const BlockNoteEditor = useBlockNote();
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| -------- | ----------------- | ------------------------ |
| محرر | `BlockNoteEditor` | مثيل أو تكوين محرر الكتل |
</Tab>
return <BlockEditor editor={BlockNoteEditor} />;
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| -------- | ----------------- | ------------------------ |
| محرر | `BlockNoteEditor` | مثيل أو تكوين محرر الكتل |
</Tab>
</Tabs>
@@ -12,428 +12,511 @@ image: /images/user-guide/views/filter.png
## زر
<Tabs>
<Tab title="27332A2E2F2745">
```jsx
import { Button } from "@/ui/input/button/components/Button";
export const MyComponent = () => {
return (
<Button
className
Icon={null}
title="Title"
fullWidth={false}
variant="primary"
size="medium"
position="standalone"
accent="default"
soon={false}
disabled={false}
focus={true}
onClick={() => console.log("click")}
/>
);
};
```
</Tab>
<Tab title="27332A2E2F2745">
```jsx
import { Button } from "@/ui/input/button/components/Button";
export const MyComponent = () => {
return (
<Button
className
Icon={null}
title="Title"
fullWidth={false}
variant="primary"
size="medium"
position="standalone"
accent="default"
soon={false}
disabled={false}
focus={true}
onClick={() => console.log("click")}
/>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | --------------------- | ---------------------------------------------------------------------------------- |
| className | string | اسم فئة اختياري لتنسيقات إضافية |
| أيقونة | `React.ComponentType` | مكون رمز اختياري يُعرض داخل الزر |
| العنوان | string | محتوى نص الزر |
| عرض كامل | قيمة منطقية | يُحدد إذا كان الزر يجب أن يمتد ليغطي العرض الكامل للحاوية الخاصة به |
| التنوع | string | النمط المرئي للزر. تشمل الخيارات `primary`، `secondary`، و`tertiary`. |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| الموقع | string | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `standalone`، `left`، `right`، و`middle`. |
| accent | string | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `default`، `blue`، `danger` |
| قريباً | قيمة منطقية | يشير إلى ما إذا كان الزر معلمًا "قريبًا" (مثل الميزات القادمة) |
| معطل | قيمة منطقية | يحدد إذا كان الزر معطل أم لا |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
| عند النقر | وظيفة | وظيفة رد فعل تنطلق عند نقر المستخدم على الزر |
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | --------------------- | ---------------------------------------------------------------------------------- |
| className | string | اسم فئة اختياري لتنسيقات إضافية |
| أيقونة | `React.ComponentType` | مكون رمز اختياري يُعرض داخل الزر |
| العنوان | string | محتوى نص الزر |
| عرض كامل | قيمة منطقية | يُحدد إذا كان الزر يجب أن يمتد ليغطي العرض الكامل للحاوية الخاصة به |
| التنوع | string | النمط المرئي للزر. تشمل الخيارات `primary`، `secondary`، و`tertiary`. |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| الموقع | string | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `standalone`، `left`، `right`، و`middle`. |
| accent | string | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `default`، `blue`، `danger` |
| قريباً | قيمة منطقية | يشير إلى ما إذا كان الزر معلمًا "قريبًا" (مثل الميزات القادمة) |
| معطل | قيمة منطقية | يحدد إذا كان الزر معطل أم لا |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
| عند النقر | وظيفة | وظيفة رد فعل تنطلق عند نقر المستخدم على الزر |
</Tab>
</Tabs>
## مجموعة الأزرار
<Tabs>
<Tab title="استخدام">
```jsx
import { Button } from "@/ui/input/button/components/Button";
import { ButtonGroup } from "@/ui/input/button/components/ButtonGroup";
<Tab title="استخدام">
```jsx
import { Button } from "@/ui/input/button/components/Button";
import { ButtonGroup } from "@/ui/input/button/components/ButtonGroup";
export const MyComponent = () => {
return (
<ButtonGroup variant="primary" size="large" accent="blue" className>
<Button
className
Icon={null}
title="Button 1"
fullWidth={false}
variant="primary"
size="medium"
position="standalone"
accent="blue"
soon={false}
disabled={false}
focus={false}
onClick={() => console.log("click")}
/>
<Button
className
Icon={null}
title="Button 2"
fullWidth={false}
variant="secondary"
size="medium"
position="left"
accent="blue"
soon={false}
disabled={false}
focus={false}
onClick={() => console.log("click")}
/>
<Button
className
Icon={null}
title="Button 3"
fullWidth={false}
variant="tertiary"
size="medium"
position="right"
accent="blue"
soon={false}
disabled={false}
focus={false}
onClick={() => console.log("click")}
/>
</ButtonGroup>
);
};
export const MyComponent = () => {
return (
<ButtonGroup variant="primary" size="large" accent="blue" className>
<Button
className
Icon={null}
title="Button 1"
fullWidth={false}
variant="primary"
size="medium"
position="standalone"
accent="blue"
soon={false}
disabled={false}
focus={false}
onClick={() => console.log("click")}
/>
<Button
className
Icon={null}
title="Button 2"
fullWidth={false}
variant="secondary"
size="medium"
position="left"
accent="blue"
soon={false}
disabled={false}
focus={false}
onClick={() => console.log("click")}
/>
<Button
className
Icon={null}
title="Button 3"
fullWidth={false}
variant="tertiary"
size="medium"
position="right"
accent="blue"
soon={false}
disabled={false}
focus={false}
onClick={() => console.log("click")}
/>
</ButtonGroup>
);
};
```
</Tab>
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------- | --------- | -------------------------------------------------------------------------------------- |
| التنوع | string | النمط المرئي للأزرار داخل المجموعة. تشمل الخيارات `primary`، `secondary`، و`tertiary`. |
| الحجم | string | حجم الأزرار داخل المجموعة. يوجد خياران: `medium` و`small`. |
| accent | نص | لون تمييز الأزرار داخل المجموعة. تشمل الخيارات `default`، `blue` و`danger`. |
| className | string | اسم فئة اختياري لتنسيقات إضافية |
| الأبناء | ReactNode | مجموعة من عناصر React تمثل الأزرار الفردية داخل المجموعة |
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------- | --------- | -------------------------------------------------------------------------------------- |
| التنوع | string | النمط المرئي للأزرار داخل المجموعة. تشمل الخيارات `primary`، `secondary`، و`tertiary`. |
| الحجم | string | حجم الأزرار داخل المجموعة. يوجد خياران: `medium` و`small`. |
| accent | نص | لون تمييز الأزرار داخل المجموعة. تشمل الخيارات `default`، `blue` و`danger`. |
| className | string | اسم فئة اختياري لتنسيقات إضافية |
| الأبناء | ReactNode | مجموعة من عناصر React تمثل الأزرار الفردية داخل المجموعة |
</Tab>
</Tabs>
## زر عائم
<Tabs>
<Tab title="الاستخدام">
```jsx
import { FloatingButton } from "@/ui/input/button/components/FloatingButton";
import { IconSearch } from "@tabler/icons-react";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<FloatingButton
className
Icon={IconSearch}
title="Title"
size="medium"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
focus={true}
/>
);
};
```
</Tab>
```jsx
import { FloatingButton } from "@/ui/input/button/components/FloatingButton";
import { IconSearch } from "@tabler/icons-react";
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------ | --------------------- | --------------------------------------------------------------------------------- |
| className | string | اسم اختياري لتنسيقات إضافية |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| العنوان | string | محتوى نص الزر |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| الموقع | string | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `standalone`، `left`، `middle`، `right`. |
| تطبيق الظل | قيمة منطقية | يحدد إذا ما سيتم تطبيق الظلال على الزر |
| تطبيق الضباب | قيمة منطقية | يحدد ما إذا كان ينبغي تطبيق تأثير الضباب على الزر |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطل |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
</Tab>
export const MyComponent = () => {
return (
<FloatingButton
className
Icon={IconSearch}
title="Title"
size="medium"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
focus={true}
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------ | --------------------- | --------------------------------------------------------------------------------- |
| className | string | اسم اختياري لتنسيقات إضافية |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| العنوان | string | محتوى نص الزر |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| الموقع | string | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `standalone`، `left`، `middle`، `right`. |
| تطبيق الظل | قيمة منطقية | يحدد إذا ما سيتم تطبيق الظلال على الزر |
| تطبيق الضباب | قيمة منطقية | يحدد ما إذا كان ينبغي تطبيق تأثير الضباب على الزر |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطل |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
</Tab>
</Tabs>
## مجموعة الأزرار العائمة
<Tabs>
<Tab title="الاستخدام">
```jsx
import { FloatingButton } from "@/ui/input/button/components/FloatingButton";
import { FloatingButtonGroup } from "@/ui/input/button/components/FloatingButtonGroup";
import { IconClipboardText, IconCheckbox } from "@tabler/icons-react";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<FloatingButtonGroup size="small">
<FloatingButton
className
Icon={IconClipboardText}
title
size="small"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
focus={true}
/>
<FloatingButton
className
Icon={IconCheckbox}
title
size="small"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
/>
</FloatingButtonGroup>
);
};
```
</Tab>
```jsx
import { FloatingButton } from "@/ui/input/button/components/FloatingButton";
import { FloatingButtonGroup } from "@/ui/input/button/components/FloatingButtonGroup";
import { IconClipboardText, IconCheckbox } from "@tabler/icons-react";
export const MyComponent = () => {
return (
<FloatingButtonGroup size="small">
<FloatingButton
className
Icon={IconClipboardText}
title
size="small"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
focus={true}
/>
<FloatingButton
className
Icon={IconCheckbox}
title
size="small"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
/>
</FloatingButtonGroup>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف | الإعداد الافتراضي |
| ------- | --------- | -------------------------------------------------------- | ----------------- |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` | صغير |
| الأبناء | ReactNode | مجموعة من عناصر React تمثل الأزرار الفردية داخل المجموعة | |
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف | الإعداد الافتراضي |
| ------- | --------- | -------------------------------------------------------- | ----------------- |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` | صغير |
| الأبناء | ReactNode | مجموعة من عناصر React تمثل الأزرار الفردية داخل المجموعة | |
</Tab>
</Tabs>
## زر رمز عائم
<Tabs>
<Tab title="الاستخدام">
```jsx
import { FloatingIconButton } from "@/ui/input/button/components/FloatingIconButton";
import { IconSearch } from "@tabler/icons-react";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<FloatingIconButton
className
Icon={IconSearch}
size="small"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
focus={false}
onClick={() => console.log("click")}
isActive={true}
/>
);
};
```
</Tab>
```jsx
import { FloatingIconButton } from "@/ui/input/button/components/FloatingIconButton";
import { IconSearch } from "@tabler/icons-react";
export const MyComponent = () => {
return (
<FloatingIconButton
className
Icon={IconSearch}
size="small"
position="standalone"
applyShadow={true}
applyBlur={true}
disabled={false}
focus={false}
onClick={() => console.log("click")}
isActive={true}
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------ | --------------------- | ---------------------------------------------------------------------------------- |
| className | نص | اسم اختياري لتنسيقات إضافية |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| الحجم | نص | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| الموقع | نص | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `standalone`، `left`، `right`، و`middle`. |
| تطبيق الظل | قيمة منطقية | يحدد إذا ما سيتم تطبيق الظلال على الزر |
| تطبيق الضباب | قيمة منطقية | يحدد ما إذا كان ينبغي تطبيق تأثير الضباب على الزر |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطل |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
| عند النقر | وظيفة | وظيفة رد فعل تنطلق عند نقر المستخدم على الزر |
| فعّال | قيمة منطقية | يحدد إذا كان الزر في وضع فعّال |
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------ | --------------------- | ---------------------------------------------------------------------------------- |
| className | نص | اسم اختياري لتنسيقات إضافية |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| الحجم | نص | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| الموقع | نص | موقع الزر بالنسبة لأخوته. تشمل الخيارات: `standalone`، `left`، `right`، و`middle`. |
| تطبيق الظل | قيمة منطقية | يحدد إذا ما سيتم تطبيق الظلال على الزر |
| تطبيق الضباب | قيمة منطقية | يحدد ما إذا كان ينبغي تطبيق تأثير الضباب على الزر |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطل |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
| عند النقر | وظيفة | وظيفة رد فعل تنطلق عند نقر المستخدم على الزر |
| فعّال | قيمة منطقية | يحدد إذا كان الزر في وضع فعّال |
</Tab>
</Tabs>
## مجموعة أزرار الرموز العائمة
<Tabs>
<Tab title="الاستخدام">
```jsx
import { FloatingIconButtonGroup } from "@/ui/input/button/components/FloatingIconButtonGroup";
import { IconClipboardText, IconCheckbox } from "@tabler/icons-react";
<Tab title="الاستخدام">
export const MyComponent = () => {
const iconButtons = [
{
Icon: IconClipboardText,
onClick: () => console.log("Button 1 clicked"),
isActive: true,
},
{
Icon: IconCheckbox,
onClick: () => console.log("Button 2 clicked"),
isActive: true,
},
];
```jsx
import { FloatingIconButtonGroup } from "@/ui/input/button/components/FloatingIconButtonGroup";
import { IconClipboardText, IconCheckbox } from "@tabler/icons-react";
return (
<FloatingIconButtonGroup
className
size="small"
iconButtons={iconButtons} />
);
};
export const MyComponent = () => {
const iconButtons = [
{
Icon: IconClipboardText,
onClick: () => console.log("Button 1 clicked"),
isActive: true,
},
{
Icon: IconCheckbox,
onClick: () => console.log("Button 2 clicked"),
isActive: true,
},
];
```
</Tab>
return (
<FloatingIconButtonGroup
className
size="small"
iconButtons={iconButtons} />
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| className | string | اسم اختياري لتنسيقات إضافية |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| أزرار الرموز | array | مجموعة من الكائنات، يمثل كل منها زر رمز في المجموعة. يجب أن يشمل كل كائن مكون الرمز الذي تريد عرضه في الزر، الوظيفة التي ترغب في استدعائها عند نقر المستخدم على الزر، وما إذا كان الزر ينبغي أن يكون نشطًا أم لا. |
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| className | string | اسم اختياري لتنسيقات إضافية |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| أزرار الرموز | array | مجموعة من الكائنات، يمثل كل منها زر رمز في المجموعة. يجب أن يشمل كل كائن مكون الرمز الذي تريد عرضه في الزر، الوظيفة التي ترغب في استدعائها عند نقر المستخدم على الزر، وما إذا كان الزر ينبغي أن يكون نشطًا أم لا. |
</Tab>
</Tabs>
## زر خفيف
<Tabs>
<Tab title="الاستخدام">
```jsx
import { LightButton } from "@/ui/input/button/components/LightButton";
<Tab title="الاستخدام">
export const MyComponent = () => {
return <LightButton
className
icon={null}
title="Title"
accent="secondary"
active={false}
disabled={false}
focus={true}
onClick={()=>console.log('click')}
/>;
};
```
</Tab>
```jsx
import { LightButton } from "@/ui/input/button/components/LightButton";
export const MyComponent = () => {
return <LightButton
className
icon={null}
title="Title"
accent="secondary"
active={false}
disabled={false}
focus={true}
onClick={()=>console.log('click')}
/>;
};
```
</Tab>
<Tab title="الإزاحة">
| الإزاحة | النوع | الوصف |
| --------- | ----------------- | -------------------------------------------------------------------------- |
| className | string | اسم اختياري لتنسيقات إضافية |
| أيقونة | `React.ReactNode` | الرمز الذي تريد عرضه في الزر |
| العنوان | string | محتوى نص الزر |
| accent | string | موقع الزر بالنسبة لأخوته. لون الزر المميز تشمل الخيارات: `ثانوي` و `ثالثي` |
| نشط | قيمة منطقية | يحدد إذا كان الزر في وضع فعّال |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطل |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
| عند النقر | وظيفة | وظيفة رد فعل تنطلق عند نقر المستخدم على الزر |
</Tab>
<Tab title="الإزاحة">
| الإزاحة | النوع | الوصف |
| --------- | ----------------- | -------------------------------------------------------------------------- |
| className | string | اسم اختياري لتنسيقات إضافية |
| أيقونة | `React.ReactNode` | الرمز الذي تريد عرضه في الزر |
| العنوان | string | محتوى نص الزر |
| accent | string | موقع الزر بالنسبة لأخوته. لون الزر المميز تشمل الخيارات: `ثانوي` و `ثالثي` |
| نشط | قيمة منطقية | يحدد إذا كان الزر في وضع فعّال |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطل |
| تركيز | قيمة منطقية | يحدد إذا كان الزر في وضع التركيز |
| عند النقر | وظيفة | وظيفة رد فعل تنطلق عند نقر المستخدم على الزر |
</Tab>
</Tabs>
## زر أيقونة خفيف
<Tabs>
<Tab title="الاستخدام">
```jsx
import { LightIconButton } from "@/ui/input/button/components/LightIconButton";
import { IconSearch } from "@tabler/icons-react";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<LightIconButton
className
testId="test1"
Icon={IconSearch}
title="Title"
size="small"
accent="secondary"
active={true}
disabled={false}
focus={true}
onClick={() => console.log("click")}
/>
);
};
```
</Tab>
```jsx
import { LightIconButton } from "@/ui/input/button/components/LightIconButton";
import { IconSearch } from "@tabler/icons-react";
export const MyComponent = () => {
return (
<LightIconButton
className
testId="test1"
Icon={IconSearch}
title="Title"
size="small"
accent="secondary"
active={true}
disabled={false}
focus={true}
onClick={() => console.log("click")}
/>
);
};
```
</Tab>
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| --------- | --------------------- | ------------------------------------------------ |
| className | string | اسم اختياري لتنسيقات إضافية |
| testId | string | معرف اختبار للزر |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| العنوان | string | محتوى نصي للزر |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| accent | string | لون الزر المميز تشمل الخيارات: `ثانوي` و `ثالثي` |
| نشط | قيمة منطقية | يحدد ما إذا كان الزر في حالة نشطة |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطلاً |
| التركيز | قيمة منطقية | يشير إلى ما إذا كان الزر لديه تركيز |
| عند النقر | function | وظيفة رد اتصال تتفعّل عند نقر المستخدم على الزر |
</Tab>
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| --------- | --------------------- | ------------------------------------------------ |
| className | string | اسم اختياري لتنسيقات إضافية |
| testId | string | معرف اختبار للزر |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| العنوان | string | محتوى نصي للزر |
| الحجم | string | حجم الزر. يوجد خياران: `صغير` و `متوسط` |
| accent | string | لون الزر المميز تشمل الخيارات: `ثانوي` و `ثالثي` |
| نشط | قيمة منطقية | يحدد ما إذا كان الزر في حالة نشطة |
| معطل | قيمة منطقية | يحدد ما إذا كان الزر معطلاً |
| التركيز | قيمة منطقية | يشير إلى ما إذا كان الزر لديه تركيز |
| عند النقر | function | وظيفة رد اتصال تتفعّل عند نقر المستخدم على الزر |
</Tab>
</Tabs>
## الزر الرئيسي
<Tabs>
<Tab title="الاستخدام">
```jsx
import { MainButton } from "@/ui/input/button/components/MainButton";
import { IconCheckbox } from "@tabler/icons-react";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<MainButton
title="Checkbox"
fullWidth={false}
variant="primary"
soon={false}
Icon={IconCheckbox}
/>
);
};
```
</Tab>
```jsx
import { MainButton } from "@/ui/input/button/components/MainButton";
import { IconCheckbox } from "@tabler/icons-react";
export const MyComponent = () => {
return (
<MainButton
title="Checkbox"
fullWidth={false}
variant="primary"
soon={false}
Icon={IconCheckbox}
/>
);
};
```
</Tab>
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| -------------- | -------------------------------- | -------------------------------------------------------------- |
| العنوان | string | محتوى نصي للزر |
| عرض كامل | قيمة منطقية | يحدد ما إذا كان الزر يجب أن يمتد على كامل عرض الحاوية |
| التنوع | string | النمط البصري للزر. تشمل الخيارات `أساسي` و `ثانوي` |
| قريباً | قيمة منطقية | يشير إلى ما إذا كان الزر معلمًا "قريبًا" (مثل الميزات القادمة) |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| خصائص زر React | `React.ComponentProps<'button'>` | كل خصائص زر HTML القياسية مدعومة |
</Tab>
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| -------------- | -------------------------------- | -------------------------------------------------------------- |
| العنوان | string | محتوى نصي للزر |
| عرض كامل | قيمة منطقية | يحدد ما إذا كان الزر يجب أن يمتد على كامل عرض الحاوية |
| التنوع | string | النمط البصري للزر. تشمل الخيارات `أساسي` و `ثانوي` |
| قريباً | قيمة منطقية | يشير إلى ما إذا كان الزر معلمًا "قريبًا" (مثل الميزات القادمة) |
| أيقونة | `React.ComponentType` | مكون أيقونة اختياري يظهر داخل الزر |
| خصائص زر React | `React.ComponentProps<'button'>` | كل خصائص زر HTML القياسية مدعومة |
</Tab>
</Tabs>
## زر أيقونة دائري
<Tabs>
<Tab title="الاستخدام">
```jsx
import { RoundedIconButton } from "@/ui/input/button/components/RoundedIconButton";
import { IconSearch } from "@tabler/icons-react";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<RoundedIconButton
Icon={IconSearch}
/>
);
};
```
</Tab>
```jsx
import { RoundedIconButton } from "@/ui/input/button/components/RoundedIconButton";
import { IconSearch } from "@tabler/icons-react";
export const MyComponent = () => {
return (
<RoundedIconButton
Icon={IconSearch}
/>
);
};
```
</Tab>
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| -------------- | ----------------------------------------------- | ----- |
| أيقونة | `React.ComponentType` | |
| خصائص زر React | `React.ButtonHTMLAttributes<HTMLButtonElement>` | |
</Tab>
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| -------------- | ----------------------------------------------- | ----- |
| أيقونة | `React.ComponentType` | |
| خصائص زر React | `React.ButtonHTMLAttributes<HTMLButtonElement>` | |
</Tab>
</Tabs>
@@ -10,35 +10,41 @@ image: /images/user-guide/tasks/tasks_header.png
يُستخدم عندما يحتاج المستخدم إلى اختيار قيم متعددة من بين عدة خيارات.
<Tabs>
<Tab title="استخدام">
```jsx
import { Checkbox } from "twenty-ui/display";
<Tab title="استخدام">
export const MyComponent = () => {
return (
<Checkbox
checked={true}
indeterminate={false}
onChange={() => console.log("onChange function fired")}
onCheckedChange={() => console.log("onCheckedChange function fired")}
variant="primary"
size="small"
shape="squared"
/>
);
};
```
</Tab>
```jsx
import { Checkbox } from "twenty-ui/display";
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------------ | ----------- | ----------------------------------------------------------------------------- |
| مختار | قيمة منطقية | يشير إلى ما إذا كان مربع الاختيار محددًا |
| غير محدد | قيمة منطقية | يشير إلى ما إذا كان مربع الاختيار في حالة غير محددة (لا هو محدد ولا غير محدد) |
| عند التغيير | دالة | الدالة التي ترغب في تفعيلها عند تغيير حالة مربع الاختيار |
| عند تغيير الحالة المحددة | دالة | الدالة التي ترغب في تفعيلها عند تغيّر حالة `checked` |
| نموذج | نص | النمط البصري للصندوق. تتضمن الخيارات: 'أساسي'، 'ثانوي'، و 'ثالثي' |
| الحجم | نص | حجم مربع الاختيار. له خياران: `small` و `large` |
| الشكل | نص | شكل مربع الاختيار. لديه خياران: 'مربع' و 'مدور' |
</Tab>
export const MyComponent = () => {
return (
<Checkbox
checked={true}
indeterminate={false}
onChange={() => console.log("onChange function fired")}
onCheckedChange={() => console.log("onCheckedChange function fired")}
variant="primary"
size="small"
shape="squared"
/>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------------ | ----------- | ----------------------------------------------------------------------------- |
| مختار | قيمة منطقية | يشير إلى ما إذا كان مربع الاختيار محددًا |
| غير محدد | قيمة منطقية | يشير إلى ما إذا كان مربع الاختيار في حالة غير محددة (لا هو محدد ولا غير محدد) |
| عند التغيير | دالة | الدالة التي ترغب في تفعيلها عند تغيير حالة مربع الاختيار |
| عند تغيير الحالة المحددة | دالة | الدالة التي ترغب في تفعيلها عند تغيّر حالة `checked` |
| نموذج | نص | النمط البصري للصندوق. تتضمن الخيارات: 'أساسي'، 'ثانوي'، و 'ثالثي' |
| الحجم | نص | حجم مربع الاختيار. له خياران: `small` و `large` |
| الشكل | نص | شكل مربع الاختيار. لديه خياران: 'مربع' و 'مدور' |
</Tab>
</Tabs>
@@ -12,28 +12,36 @@ image: /images/user-guide/fields/field.png
يمثل مخططات ألوان مختلفة ومخصص بشكل خاص للمواضيع الفاتحة والداكنة.
<Tabs>
<Tab title="27332A2E2F2745">
```jsx
import { ColorSchemeCard } from "twenty-ui/display";
<Tab title="27332A2E2F2745">
export const MyComponent = () => {
return (
<ColorSchemeCard
variant="Dark"
selected={true}
/>
);
};
```
</Tab>
```jsx
import { ColorSchemeCard } from "twenty-ui/display";
export const MyComponent = () => {
return (
<ColorSchemeCard
variant="Dark"
selected={true}
/>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف | الإعداد الافتراضي |
| ------------ | --------------------------------------- | ---------------------------------------------------------------------- | ----------------- |
| التنوع | string | نوع مخطط الألوان. تشمل الخيارات `داكنة`, `فاتحة`, و `النظام` | فاتح |
| المحدد | قيمة منطقية | إذا كان `صحيح`, يتم عرض علامة الاختيار للدلالة على مخطط الألوان المحدد | |
| خصائص إضافية | `React.ComponentPropsWithoutRef<'div'>` | خصائص عنصر `div` العادي في HTML | |
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف | الإعداد الافتراضي |
| ------------ | --------------------------------------- | ---------------------------------------------------------------------- | ----------------- |
| التنوع | string | نوع مخطط الألوان. تشمل الخيارات `داكنة`, `فاتحة`, و `النظام` | فاتح |
| المحدد | قيمة منطقية | إذا كان `صحيح`, يتم عرض علامة الاختيار للدلالة على مخطط الألوان المحدد | |
| خصائص إضافية | `React.ComponentPropsWithoutRef<'div'>` | خصائص عنصر `div` العادي في HTML | |
</Tab>
</Tabs>
## منتقي مخطط الألوان
@@ -41,23 +49,31 @@ image: /images/user-guide/fields/field.png
يتيح للمستخدمين اختيار بين مخططات الألوان المختلفة.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { ColorSchemePicker } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
return <ColorSchemePicker
value="Dark"
onChange
/>;
};
```
</Tab>
```jsx
import { ColorSchemePicker } from "twenty-ui/display";
export const MyComponent = () => {
return <ColorSchemePicker
value="Dark"
onChange
/>;
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ----------- | ------------------- | ------------------------------------------------------------- |
| القيمة | `طريقة عرض الألوان` | مخطط الألوان المحدد حاليًا |
| عند التغيير | function | الدالة التي ترغب في تفعيلها عندما يختار المستخدم نظام الألوان |
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ----------- | ------------------- | ------------------------------------------------------------- |
| القيمة | `طريقة عرض الألوان` | مخطط الألوان المحدد حاليًا |
| عند التغيير | function | الدالة التي ترغب في تفعيلها عندما يختار المستخدم نظام الألوان |
</Tab>
</Tabs>
@@ -10,43 +10,49 @@ image: /images/user-guide/github/github-header.png
منتقى الأيقونات المعتمد على القائمة المنسدلة الذي يتيح للمستخدمين اختيار أيقونة من قائمة.
<Tabs>
<Tab title="استخدام">
```jsx
import { RecoilRoot } from "recoil";
import React, { useState } from "react";
import { IconPicker } from "@/ui/input/components/IconPicker";
export const MyComponent = () => {
<Tab title="استخدام">
const [selectedIcon, setSelectedIcon] = useState("");
const handleIconChange = ({ iconKey, Icon }) => {
console.log("Selected Icon:", iconKey);
setSelectedIcon(iconKey);
};
```jsx
import React, { useState } from "react";
import { IconPicker } from "@/ui/input/components/IconPicker";
return (
<RecoilRoot>
<IconPicker
disabled={false}
onChange={handleIconChange}
selectedIconKey={selectedIcon}
variant="primary"
/>
</RecoilRoot>
);
};
```
</Tab>
export const MyComponent = () => {
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------ |
| معطل | قيمة منطقية | يقوم بتعطيل منتقى الأيقونات إذا تم تعيينه إلى `true` |
| عند التغيير | دالة | الدالة الارتجاعية التي تُفعل عندما يختار المستخدم أيقونة. يستقبل كائنًا يحتوي على الخصائص `iconKey` و `Icon` |
| مفتاح الأيقونة المختارة | نص | مفتاح الأيقونة المختارة في البداية |
| النقر بالخارج | دالة | الدالة الارتجاعية التي تُفعل عندما ينقر المستخدم خارج القائمة المنسدلة |
| عند الإغلاق | دالة | الدالة الارتجاعية التي تُفعل عند إغلاق القائمة المنسدلة |
| عند الفتح | دالة | الدالة الارتجاعية التي تُفعل عند فتح القائمة المنسدلة |
| التنوع | نص | متغير النمط البصري للأيقونة القابلة للنقر. تشمل الخيارات: `رئيسي`, `ثانوي`, و `ثالثي` |
</Tab>
const [selectedIcon, setSelectedIcon] = useState("");
const handleIconChange = ({ iconKey, Icon }) => {
console.log("Selected Icon:", iconKey);
setSelectedIcon(iconKey);
};
return (
<IconPicker
disabled={false}
onChange={handleIconChange}
selectedIconKey={selectedIcon}
variant="primary"
/>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------ |
| معطل | قيمة منطقية | يقوم بتعطيل منتقى الأيقونات إذا تم تعيينه إلى `true` |
| عند التغيير | دالة | الدالة الارتجاعية التي تُفعل عندما يختار المستخدم أيقونة. يستقبل كائنًا يحتوي على الخصائص `iconKey` و `Icon` |
| مفتاح الأيقونة المختارة | نص | مفتاح الأيقونة المختارة في البداية |
| النقر بالخارج | دالة | الدالة الارتجاعية التي تُفعل عندما ينقر المستخدم خارج القائمة المنسدلة |
| عند الإغلاق | دالة | الدالة الارتجاعية التي تُفعل عند إغلاق القائمة المنسدلة |
| عند الفتح | دالة | الدالة الارتجاعية التي تُفعل عند فتح القائمة المنسدلة |
| التنوع | نص | متغير النمط البصري للأيقونة القابلة للنقر. تشمل الخيارات: `رئيسي`, `ثانوي`, و `ثالثي` |
</Tab>
</Tabs>
@@ -10,25 +10,32 @@ image: /images/user-guide/objects/objects.png
4A4F33452D 44445245332A2E2F454A46 28452F 482532274429 35483129.
<Tabs>
<Tab title="استخدام">
```jsx
import { ImageInput } from "@/ui/input/components/ImageInput";
<Tab title="استخدام">
export const MyComponent = () => {
return <ImageInput/>;
};
```
</Tab>
```jsx
import { ImageInput } from "@/ui/input/components/ImageInput";
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------ | ----------- | --------------------------------------------------------------------------------- |
| صورة | نص | 3946482746 45352F31 274435483129 27442544432A3148464A |
| onUpload | دالة | الدالة التي تُستدعى عند قيام المستخدم بتحميل صورة جديدة. تستقبل كائن `File` كوسيط |
| onRemove | دالة | الدالة التي تُستدعى عند نقر المستخدم على زر الإزالة |
| onAbort | دالة | الدالة التي تُستدعى عند نقر المستخدم على زر الإلغاء أثناء تحميل الصورة |
| isUploading | قيمة منطقية | يشير إلى ما إذا كان يتم تحميل صورة حاليًا |
| errorMessage | نص | رسالة خطأ اختيارية لعرضها أسفل حقل إدخال الصورة |
| معطل | قيمة منطقية | إذا كانت `true`، فسيكون حقل الإدخال بأكمله معطلاً، ولن تكون الأزرار قابلة للنقر |
</Tab>
export const MyComponent = () => {
return <ImageInput/>;
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------ | ----------- | --------------------------------------------------------------------------------- |
| صورة | نص | 3946482746 45352F31 274435483129 27442544432A3148464A |
| onUpload | دالة | الدالة التي تُستدعى عند قيام المستخدم بتحميل صورة جديدة. تستقبل كائن `File` كوسيط |
| onRemove | دالة | الدالة التي تُستدعى عند نقر المستخدم على زر الإزالة |
| onAbort | دالة | الدالة التي تُستدعى عند نقر المستخدم على زر الإلغاء أثناء تحميل الصورة |
| isUploading | قيمة منطقية | يشير إلى ما إذا كان يتم تحميل صورة حاليًا |
| errorMessage | نص | رسالة خطأ اختيارية لعرضها أسفل حقل إدخال الصورة |
| معطل | قيمة منطقية | إذا كانت `true`، فسيكون حقل الإدخال بأكمله معطلاً، ولن تكون الأزرار قابلة للنقر |
</Tab>
</Tabs>
@@ -10,50 +10,58 @@ image: /images/user-guide/create-workspace/workspace-cover.png
تستخدم عندما يمكن للمستخدمين اختيار خيار واحد فقط من سلسلة من الخيارات.
<Tabs>
<Tab title="استخدام">
```jsx
import { Radio } from "twenty-ui/display";
<Tab title="استخدام">
export const MyComponent = () => {
```jsx
import { Radio } from "twenty-ui/display";
const handleRadioChange = (event) => {
console.log("Radio button changed:", event.target.checked);
};
export const MyComponent = () => {
const handleCheckedChange = (checked) => {
console.log("Checked state changed:", checked);
};
const handleRadioChange = (event) => {
console.log("Radio button changed:", event.target.checked);
};
const handleCheckedChange = (checked) => {
console.log("Checked state changed:", checked);
};
return (
<Radio
checked={true}
value="Option 1"
onChange={handleRadioChange}
onCheckedChange={handleCheckedChange}
size="large"
disabled={false}
labelPosition="right"
/>
);
};
},{
```
</Tab>
return (
<Radio
checked={true}
value="Option 1"
onChange={handleRadioChange}
onCheckedChange={handleCheckedChange}
size="large"
disabled={false}
labelPosition="right"
/>
);
};
},{
```
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------------ | ----------------- | ------------------------------------------------------------------ |
| النمط | خصائص `React.CSS` | أنماط إضافية مضمنة للمكون |
| اسم الفئة | نص | فئة CSS اختيارية لتصميم إضافي |
| مختار | قيمة منطقية | يشير إلى ما إذا كان زر الراديو محددًا |
| القيمة | نص | التسمية أو النص المرتبط بزر الراديو |
| عند التغيير | دالة | الدالة التي تُستدعى عند تغيير زر الاختيار المحدد. |
| عند تغيير الحالة المحددة | دالة | الدالة التي تُستدعى عند تغيير حالة `checked` لزر الاختيار. |
| الحجم | نص | حجم زر الراديو. تشمل الخيارات: `large` و `small` |
| معطل | قيمة منطقية | إذا كانت `true`، فسيكون زر الاختيار معطلاً وغير قابل للنقر |
| موضع التسمية | نص | موضع نص التسمية بالنسبة لزر الراديو. يوجد خياران: `left` و `right` |
</Tab>
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------------------ | ----------------- | ------------------------------------------------------------------ |
| النمط | خصائص `React.CSS` | أنماط إضافية مضمنة للمكون |
| اسم الفئة | نص | فئة CSS اختيارية لتصميم إضافي |
| مختار | قيمة منطقية | يشير إلى ما إذا كان زر الراديو محددًا |
| القيمة | نص | التسمية أو النص المرتبط بزر الراديو |
| عند التغيير | دالة | الدالة التي تُستدعى عند تغيير زر الاختيار المحدد. |
| عند تغيير الحالة المحددة | دالة | الدالة التي تُستدعى عند تغيير حالة `checked` لزر الاختيار. |
| الحجم | نص | حجم زر الراديو. تشمل الخيارات: `large` و `small` |
| معطل | قيمة منطقية | إذا كانت `true`، فسيكون زر الاختيار معطلاً وغير قابل للنقر |
| موضع التسمية | نص | موضع نص التسمية بالنسبة لزر الراديو. يوجد خياران: `left` و `right` |
</Tab>
</Tabs>
## مجموعة الراديو
@@ -61,37 +69,45 @@ image: /images/user-guide/create-workspace/workspace-cover.png
يجمع أزرار الراديو ذات الصلة معًا.
<Tabs>
<Tab title="استخدام">
```jsx
import React, { useState } from "react";
import { Radio, RadioGroup } from "twenty-ui/display";
<Tab title="استخدام">
export const MyComponent = () => {
```jsx
import React, { useState } from "react";
import { Radio, RadioGroup } from "twenty-ui/display";
const [selectedValue, setSelectedValue] = useState("Option 1");
export const MyComponent = () => {
const handleChange = (event) => {
setSelectedValue(event.target.value);
};
return (
<RadioGroup value={selectedValue} onChange={handleChange}>
<Radio value="Option 1" />
<Radio value="Option 2" />
<Radio value="Option 3" />
</RadioGroup>
);
};
const [selectedValue, setSelectedValue] = useState("Option 1");
```
</Tab>
const handleChange = (event) => {
setSelectedValue(event.target.value);
};
return (
<RadioGroup value={selectedValue} onChange={handleChange}>
<Radio value="Option 1" />
<Radio value="Option 2" />
<Radio value="Option 3" />
</RadioGroup>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------- | ----------------- | --------------------------------------------------------------------- |
| القيمة | string | قيمة زر الراديو المحدد حاليًا |
| عند التغيير | دالة | دالة الاستدعاء التي يتم تشغيلها عند تغيير زر الاختيار. |
| onValueChange | دالة | دالة الاستدعاء التي يتم تشغيلها عند تغيير القيمة المحددة في المجموعة. |
| الأبناء | `React.ReactNode` | يتيح لك تمرير مكوّنات React (مثل Radio) كعناصر فرعية إلى Radio Group |
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| ------------- | ----------------- | --------------------------------------------------------------------- |
| القيمة | string | قيمة زر الراديو المحدد حاليًا |
| عند التغيير | دالة | دالة الاستدعاء التي يتم تشغيلها عند تغيير زر الاختيار. |
| onValueChange | دالة | دالة الاستدعاء التي يتم تشغيلها عند تغيير القيمة المحددة في المجموعة. |
| الأبناء | `React.ReactNode` | يتيح لك تمرير مكوّنات React (مثل Radio) كعناصر فرعية إلى Radio Group |
</Tab>
</Tabs>
@@ -10,42 +10,46 @@ image: /images/user-guide/what-is-twenty/20.png
يتيح للمستخدمين اختيار قيمة من قائمة من الخيارات المحددة مسبقًا.
<Tabs>
<Tab title="استخدام">
```jsx
import { RecoilRoot } from 'recoil';
import { IconTwentyStar } from 'twenty-ui/display';
<Tab title="استخدام">
import { Select } from '@/ui/input/components/Select';
```jsx
import { IconTwentyStar } from 'twenty-ui/display';
export const MyComponent = () => {
import { Select } from '@/ui/input/components/Select';
return (
<RecoilRoot>
<Select
className
disabled={false}
label="Select an option"
options={[
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
]}
value="option1"
/>
</RecoilRoot>
);
};
export const MyComponent = () => {
```
</Tab>
return (
<Select
className
disabled={false}
label="Select an option"
options={[
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
]}
value="option1"
/>
);
};
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ----------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| اسم الفئة | نص | فئة CSS اختيارية للتنسيق الإضافي |
| معطل | قيمة منطقية | عند ضبطها على `true`، يتم تعطيل تفاعل المستخدم مع المكون |
| التسمية | نص | التسمية التي تصف غرض مكوّن `Select` |
| عند التغيير | دالة | الدالة التي تُستدعى عند تغيير القيم المحددة |
| خيارات | مصفوفة | تمثل الخيارات المتاحة في مكون `الاختيار`. إنها مصفوفة من الكائنات حيث يحتوي كل كائن على `قيمة` (معرف فريد)، `تسمية` (معرف فريد)، و`أيقونة` اختيارية |
| القيمة | نص | تمثل القيمة المحددة حاليًا. يجب أن تطابق إحدى خصائص `القيمة` في مصفوفة `الخيارات` |
</Tab>
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ----------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| اسم الفئة | نص | فئة CSS اختيارية للتنسيق الإضافي |
| معطل | قيمة منطقية | عند ضبطها على `true`، يتم تعطيل تفاعل المستخدم مع المكون |
| التسمية | نص | التسمية التي تصف غرض مكوّن `Select` |
| عند التغيير | دالة | الدالة التي تُستدعى عند تغيير القيم المحددة |
| خيارات | مصفوفة | تمثل الخيارات المتاحة في مكون `الاختيار`. إنها مصفوفة من الكائنات حيث يحتوي كل كائن على `قيمة` (معرف فريد)، `تسمية` (معرف فريد)، و`أيقونة` اختيارية |
| القيمة | نص | تمثل القيمة المحددة حاليًا. يجب أن تطابق إحدى خصائص `القيمة` في مصفوفة `الخيارات` |
</Tab>
</Tabs>
@@ -12,53 +12,61 @@ image: /images/user-guide/notes/notes_header.png
يسمح للمستخدمين بإدخال وتحرير النص.
<Tabs>
<Tab title="27332A2E2F2745">
```jsx
import { RecoilRoot } from "recoil";
import { TextInput } from "@/ui/input/components/TextInput";
export const MyComponent = () => {
const handleChange = (text) => {
console.log("تم تغيير الإدخال:", text);
};
<Tab title="27332A2E2F2745">
const handleKeyDown = (event) => {
console.log("تم ضغط المفتاح:", event.key);
};
```jsx
import { TextInput } from "@/ui/input/components/TextInput";
return (
<RecoilRoot>
<TextInput
className
label="اسم المستخدم"
onChange={handleChange}
fullWidth={false}
disableHotkeys={false}
error="اسم مستخدم غير صالح"
onKeyDown={handleKeyDown}
RightIcon={null}
/>
</RecoilRoot>
);
};
},{
```
</Tab>
export const MyComponent = () => {
const handleChange = (text) => {
console.log("Input changed:", text);
};
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------- |
| className | string | اسم اختياري للتنسيق الإضافي. |
| التسمية | نص | يمثل التسمية للإدخال. |
| onChange | وظيفة | الدالة التي تُستدعى عند تغيير قيمة الإدخال. |
| عرض كامل | قيمة منطقية | يشير إلى ما إذا كان الإدخال يجب أن يشغل 100% من العرض. |
| تعطيل الإختصارات | قيمة منطقية | يشير إلى ما إذا كانت الاختصارات ممكنة للإدخال. |
| خطأ | string | يمثل رسالة الخطأ التي سيتم عرضها. عند توفرها، تضيف رمز خطأ على الجانب الأيمن من الإدخال. |
| onKeyDown | دالة | يتم الاستدعاء عندما يتم الضغط على مفتاح عند التركيز على حقل الإدخال. يتلقى `React.KeyboardEvent` كمعلمة |
| أيقونة يمين | مكون الأيقونة | مكون أيقونة اختياري معروض على الجانب الأيمن من الإدخال. |
const handleKeyDown = (event) => {
console.log("Key pressed:", event.key);
};
return (
<TextInput
className
label="Username"
onChange={handleChange}
fullWidth={false}
disableHotkeys={false}
error="Invalid username"
onKeyDown={handleKeyDown}
RightIcon={null}
/>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------- |
| className | string | اسم اختياري للتنسيق الإضافي. |
| التسمية | نص | يمثل التسمية للإدخال. |
| onChange | وظيفة | الدالة التي تُستدعى عند تغيير قيمة الإدخال. |
| عرض كامل | قيمة منطقية | يشير إلى ما إذا كان الإدخال يجب أن يشغل 100% من العرض. |
| تعطيل الإختصارات | قيمة منطقية | يشير إلى ما إذا كانت الاختصارات ممكنة للإدخال. |
| خطأ | string | يمثل رسالة الخطأ التي سيتم عرضها. عند توفرها، تضيف رمز خطأ على الجانب الأيمن من الإدخال. |
| onKeyDown | دالة | يتم الاستدعاء عندما يتم الضغط على مفتاح عند التركيز على حقل الإدخال. يتلقى `React.KeyboardEvent` كمعلمة |
| أيقونة يمين | مكون الأيقونة | مكون أيقونة اختياري معروض على الجانب الأيمن من الإدخال. |
يقبل المكون أيضًا دعم خصائص HTML أخرى لعناصر الإدخال.
</Tab>
يقبل المكون أيضًا دعم خصائص HTML أخرى لعناصر الإدخال.
</Tab>
</Tabs>
## إدخال نص بالحجم التلقائي
@@ -66,40 +74,48 @@ image: /images/user-guide/notes/notes_header.png
مكون إدخال نصي يعدل ارتفاعه تلقائيًا بناءً على المحتوى.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { RecoilRoot } from "recoil";
import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
export const MyComponent = () => {
return (
<RecoilRoot>
<AutosizeTextInput
onValidate={() => console.log("تم تشغيل الدالة onValidate")}
minRows={1}
placeholder="اكتب تعليقًا"
onFocus={() => console.log("تم تشغيل الدالة onFocus")}
variant="icon"
buttonTitle
value="المهمة: "
/>
</RecoilRoot>
);
};},{
```
</Tab>
<Tab title="الاستخدام">
```jsx
import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
export const MyComponent = () => {
return (
<AutosizeTextInput
onValidate={() => console.log("onValidate function fired")}
minRows={1}
placeholder="Write a comment"
onFocus={() => console.log("onFocus function fired")}
variant="icon"
buttonTitle
value="Task: "
/>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ------------------ | ----- | ------------------------------------------------------------- |
| onValidate | دالة | الدالة التي ترغب في تفعيلها عند تصديق المستخدم الإدخال. |
| الحد الأدنى للأسطر | رقم | عدد الأسطر الأدنى للمساحة النصية. |
| النص التوضيحي | نص | النص التوضيحي الذي ترغب في عرضه عند كون المساحة النصية فارغة. |
| onFocus | دالة | الدالة التي ترغب في تفعيلها عند تركيز المساحة النصية. |
| البديل | نص | البديل للإدخال. تشمل الخيارات: `افتراضي`، `أيقونة`، و`زر`. |
| عنوان الزر | نص | العنوان للزر (فقط للبديل الزر). |
| القيمة | نص | القيمة الأولية للمساحة النصية. |
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ------------------ | ----- | ------------------------------------------------------------- |
| onValidate | دالة | الدالة التي ترغب في تفعيلها عند تصديق المستخدم الإدخال. |
| الحد الأدنى للأسطر | رقم | عدد الأسطر الأدنى للمساحة النصية. |
| النص التوضيحي | نص | النص التوضيحي الذي ترغب في عرضه عند كون المساحة النصية فارغة. |
| onFocus | دالة | الدالة التي ترغب في تفعيلها عند تركيز المساحة النصية. |
| البديل | نص | البديل للإدخال. تشمل الخيارات: `افتراضي`، `أيقونة`، و`زر`. |
| عنوان الزر | نص | العنوان للزر (فقط للبديل الزر). |
| القيمة | نص | القيمة الأولية للمساحة النصية. |
</Tab>
</Tabs>
## مساحة نصية
@@ -107,31 +123,40 @@ image: /images/user-guide/notes/notes_header.png
تتيح لك إنشاء إدخالات نصية متعددة الأسطر.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { TextArea } from "@/ui/input/components/TextArea";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<TextArea
disabled={false}
minRows={4}
onChange={()=>console.log('تم تشغيل الدالة onChange')}
placeholder="أدخل النص هنا"
value=""
/>
);
};
```
</Tab>
```jsx
import { TextArea } from "@/ui/input/components/TextArea";
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ------------------ | ----------- | ------------------------------------------------ |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كانت المساحة النصية معطلة. |
| الحد الأدنى للأسطر | رقم | العدد الأدنى للأسطر الظاهرة للمساحة النصية. |
| onChange | وظيفة | دالة الاستدعاء تُشغّل عند تغيّر محتوى منطقة النص |
| نص توضيحي | نص | النص المُوضّح عندما تكون منطقة النص فارغة |
| القيمة | نص | القيمة الحالية لمنطقة النص |
</Tab>
export const MyComponent = () => {
return (
<TextArea
disabled={false}
minRows={4}
onChange={()=>console.log('تم تشغيل الدالة onChange')}
placeholder="أدخل النص هنا"
value=""
/>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| ------------------ | ----------- | ------------------------------------------------ |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كانت المساحة النصية معطلة. |
| الحد الأدنى للأسطر | رقم | العدد الأدنى للأسطر الظاهرة للمساحة النصية. |
| onChange | وظيفة | دالة الاستدعاء تُشغّل عند تغيّر محتوى منطقة النص |
| نص توضيحي | نص | النص المُوضّح عندما تكون منطقة النص فارغة |
| القيمة | نص | القيمة الحالية لمنطقة النص |
</Tab>
</Tabs>
@@ -8,29 +8,35 @@ image: /images/user-guide/table-views/table.png
</Frame>
<Tabs>
<Tab title="الاستخدام">
```jsx
import { Toggle } from "twenty-ui/input";
<Tab title="الاستخدام">
export const MyComponent = () => {
return (
<Toggle
value = {true}
onChange = {()=>console.log('تم تشغيل حدث onChange')}
color="green"
toggleSize = "medium"
/>
);
};
```
</Tab>
```jsx
import { Toggle } from "twenty-ui/input";
<Tab title="الخصائص">
| الخصائص | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ----------- | --------------------------------------------------------------------------- | ----------------- |
| القيمة | قيمة منطقية | الحالة الحالية لمفتاح التبديل | `خاطئ` |
| عند التغيير | دالة | دالة الاستدعاء التي يتم تحفيزها عند تغيير حالة مفتاح التبديل | |
| اللون | string | لون التبديل عند كونه | لون أزرق |
| حجم التبديل | نص | حجم التبديل الذي يؤثر على كل من الطول والوزن. لديها خياران: `صغير` و`متوسط` | متوسط |
</Tab>
export const MyComponent = () => {
return (
<Toggle
value = {true}
onChange = {()=>console.log('تم تشغيل حدث onChange')}
color="green"
toggleSize = "medium"
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف | الإعداد الافتراضي |
| ----------- | ----------- | --------------------------------------------------------------------------- | ----------------- |
| القيمة | قيمة منطقية | الحالة الحالية لمفتاح التبديل | `خاطئ` |
| عند التغيير | دالة | دالة الاستدعاء التي يتم تحفيزها عند تغيير حالة مفتاح التبديل | |
| اللون | string | لون التبديل عند كونه | لون أزرق |
| حجم التبديل | نص | حجم التبديل الذي يؤثر على كل من الطول والوزن. لديها خياران: `صغير` و`متوسط` | متوسط |
</Tab>
</Tabs>
@@ -10,32 +10,38 @@ image: /images/user-guide/fields/field.png
يعرض شريط مسار التنقّل.
<Tabs>
<Tab title="استخدام">
```jsx
import { BrowserRouter } from "react-router-dom";
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
<Tab title="استخدام">
export const MyComponent = () => {
const breadcrumbLinks = [
{ children: "Home", href: "/" },
{ children: "Category", href: "/category" },
{ children: "Subcategory", href: "/category/subcategory" },
{ children: "Current Page" },
];
```jsx
import { BrowserRouter } from "react-router-dom";
import { Breadcrumb } from "@/ui/navigation/bread-crumb/components/Breadcrumb";
return (
<BrowserRouter>
<Breadcrumb className links={breadcrumbLinks} />
</BrowserRouter>
)
};
```
</Tab>
export const MyComponent = () => {
const breadcrumbLinks = [
{ children: "Home", href: "/" },
{ children: "Category", href: "/category" },
{ children: "Subcategory", href: "/category/subcategory" },
{ children: "Current Page" },
];
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| اسم الفئة | نص | اسم فئة اختياري لتنسيقات إضافية |
| روابط | مصفوفة | مصفوفة من الكائنات، يمثّل كلٌّ منها رابطًا في مسار التنقّل. كل كائن يحتوي على خاصية `children` (محتوى النص للرابط) وخاصية `href` اختيارية (رابط URL للتنقل إليه عند النقر على الرابط) |
</Tab>
return (
<BrowserRouter>
<Breadcrumb className links={breadcrumbLinks} />
</BrowserRouter>
)
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| اسم الفئة | نص | اسم فئة اختياري لتنسيقات إضافية |
| روابط | مصفوفة | مصفوفة من الكائنات، يمثّل كلٌّ منها رابطًا في مسار التنقّل. كل كائن يحتوي على خاصية `children` (محتوى النص للرابط) وخاصية `href` اختيارية (رابط URL للتنقل إليه عند النقر على الرابط) |
</Tab>
</Tabs>
@@ -12,40 +12,49 @@ image: /images/user-guide/what-is-twenty/20.png
مكون رابط منمق لعرض معلومات الاتصال.
<Tabs>
<Tab title="27332A2E2F2745">
```jsx
import { BrowserRouter as Router } from 'react-router-dom';
<Tab title="27332A2E2F2745">
import { ContactLink } from 'twenty-ui/navigation';
```jsx
import { BrowserRouter as Router } from 'react-router-dom';
export const MyComponent = () => {
const handleLinkClick = (event) => {
console.log('تم النقر على رابط الاتصال!', event);
};
import { ContactLink } from 'twenty-ui/navigation';
return (
<Router>
<ContactLink
className
href="mailto:example@example.com"
onClick={handleLinkClick}
>
example@example.com
</ContactLink>
</Router>
);
};},{
```
</Tab>
export const MyComponent = () => {
const handleLinkClick = (event) => {
console.log('تم النقر على رابط الاتصال!', event);
};
return (
<Router>
<ContactLink
className
href="mailto:example@example.com"
onClick={handleLinkClick}
>
example@example.com
</ContactLink>
</Router>
);
};},{
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | ------------------------------------------------ |
| className | string | اسم اختياري للتنسيق الإضافي. |
| رابط | نص | عنوان URL المستهدف أو المسار للرابط |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | ------------------------------------------------ |
| className | string | اسم اختياري للتنسيق الإضافي. |
| رابط | نص | عنوان URL المستهدف أو المسار للرابط |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
</Tab>
</Tabs>
## رابط خام
@@ -53,36 +62,44 @@ image: /images/user-guide/what-is-twenty/20.png
مكون رابط منمق لعرض الروابط.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { RawLink } from "/navigation";
import { BrowserRouter as Router } from "react-router-dom";
<Tab title="الاستخدام">
export const MyComponent = () => {
const handleLinkClick = (event) => {
console.log("Contact link clicked!", event);
};
```jsx
import { RawLink } from "/navigation";
import { BrowserRouter as Router } from "react-router-dom";
return (
<Router>
<RawLink className href="/contact" onClick={handleLinkClick}>
Contact Us
</RawLink>
</Router>
);
};
export const MyComponent = () => {
const handleLinkClick = (event) => {
console.log("Contact link clicked!", event);
};
```
</Tab>
return (
<Router>
<RawLink className href="/contact" onClick={handleLinkClick}>
Contact Us
</RawLink>
</Router>
);
};
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | ------------------------------------------------ |
| اسم الصنف | string | اسم اختياري لتنسيقات إضافية |
| رابط | string | عنوان URL المستهدف أو المسار للرابط |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
</Tab>
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | ------------------------------------------------ |
| اسم الصنف | string | اسم اختياري لتنسيقات إضافية |
| رابط | string | عنوان URL المستهدف أو المسار للرابط |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
</Tab>
</Tabs>
## رابط مستدير
@@ -90,34 +107,41 @@ image: /images/user-guide/what-is-twenty/20.png
رابط مستدير مثبت مع مكون Chip للروابط.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { RoundedLink } from "/navigation";
import { BrowserRouter as Router } from "react-router-dom";
<Tab title="الاستخدام">
export const MyComponent = () => {
const handleLinkClick = (event) => {
console.log("Contact link clicked!", event);
};
```jsx
import { RoundedLink } from "/navigation";
import { BrowserRouter as Router } from "react-router-dom";
return (
<Router>
<RoundedLink href="/contact" onClick={handleLinkClick}>
Contact Us
</RoundedLink>
</Router>
);
};
```
</Tab>
export const MyComponent = () => {
const handleLinkClick = (event) => {
console.log("Contact link clicked!", event);
};
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | ------------------------------------------------ |
| رابط | string | عنوان URL المستهدف أو المسار للرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
</Tab>
return (
<Router>
<RoundedLink href="/contact" onClick={handleLinkClick}>
Contact Us
</RoundedLink>
</Router>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | ------------------------------------------------ |
| رابط | string | عنوان URL المستهدف أو المسار للرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
</Tab>
</Tabs>
## رابط التواصل الاجتماعي
@@ -125,30 +149,37 @@ image: /images/user-guide/what-is-twenty/20.png
روابط اجتماعية منمقة، مع دعم لأنواع متعددة من الروابط الاجتماعية، مثل العناوين الإلكترونية، LinkedIn، وX (أو Twitter).
<Tabs>
<Tab title="استخدام">
```jsx
import { SocialLink } from "twenty-ui/navigation";
import { BrowserRouter as Router } from "react-router-dom";
<Tab title="استخدام">
export const MyComponent = () => {
return (
<Router>
<SocialLink
type="twitter"
href="https://twitter.com/twentycrm"
></SocialLink>
</Router>
);
};
```
</Tab>
```jsx
import { SocialLink } from "twenty-ui/navigation";
import { BrowserRouter as Router } from "react-router-dom";
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | -------------------------------------------------------------------- |
| رابط | string | عنوان URL المستهدف أو المسار للرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
| النوع | string | نوع الروابط الاجتماعية. تشمل الخيارات: `url`, `LinkedIn`, و`Twitter` |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
</Tab>
export const MyComponent = () => {
return (
<Router>
<SocialLink
type="twitter"
href="https://twitter.com/twentycrm"
></SocialLink>
</Router>
);
};
```
</Tab>
<Tab title="خصائص">
| خصائص | النوع | الوصف |
| --------- | ----------------- | -------------------------------------------------------------------- |
| رابط | string | عنوان URL المستهدف أو المسار للرابط |
| الأبناء | `React.ReactNode` | المحتوى ليتم عرضه داخل الرابط |
| النوع | string | نوع الروابط الاجتماعية. تشمل الخيارات: `url`, `LinkedIn`, و`Twitter` |
| عند النقر | وظيفة | دالة رد النداء ليتم تفعيلها عند النقر على الرابط |
</Tab>
</Tabs>
@@ -10,49 +10,57 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة متعدد الاستخدامات مصمم للاستخدام في قائمة أو قائمة تنقل.
<Tabs>
<Tab title="استخدام">
```jsx
import { IconBell } from "@tabler/icons-react";
import { IconAlertCircle } from "@tabler/icons-react";
import { MenuItem } from "twenty-ui/display";
<Tab title="استخدام">
export const MyComponent = () => {
const handleMenuItemClick = (event) => {
console.log("Menu item clicked!", event);
};
```jsx
import { IconBell } from "@tabler/icons-react";
import { IconAlertCircle } from "@tabler/icons-react";
import { MenuItem } from "twenty-ui/display";
const handleButtonClick = (event) => {
console.log("Icon button clicked!", event);
};
export const MyComponent = () => {
const handleMenuItemClick = (event) => {
console.log("Menu item clicked!", event);
};
return (
<MenuItem
LeftIcon={IconBell}
accent="default"
text="Menu item text"
iconButtons={[{ Icon: IconAlertCircle, onClick: handleButtonClick }]}
isTooltipOpen={true}
testId="menu-item-1"
onClick={handleMenuItemClick}
className
/>
);
};
```
</Tab>
const handleButtonClick = (event) => {
console.log("Icon button clicked!", event);
};
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| -------------- | ------------- | ---------------------------------------------------------------------------- |
| أيقونة اليسار | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| accent | نص | يحدد لون الإبراز لعنصر القائمة. تشمل الخيارات: `افتراضي`, `خطر`, `موضع مؤقت` |
| نص | نص | المحتوى النصي لعنصر القائمة |
| أزرار الأيقونة | array | مجموعة من الكائنات التي تمثل أيقونات إضافية مرتبطة بعنصر القائمة |
| isTooltipOpen | قيمة منطقية | يتحكم في ظهور تلميح الأدوات المرتبط بعنصر القائمة |
| معرف الفحص | نص | السمة data-testid لأغراض الاختبار |
| عند النقر | function | دالة الاستدعاء يتم تنشيطها عند النقر فوق عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لتصميم إضافي |
</Tab>
return (
<MenuItem
LeftIcon={IconBell}
accent="default"
text="Menu item text"
iconButtons={[{ Icon: IconAlertCircle, onClick: handleButtonClick }]}
isTooltipOpen={true}
testId="menu-item-1"
onClick={handleMenuItemClick}
className
/>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| -------------- | ------------- | ---------------------------------------------------------------------------- |
| أيقونة اليسار | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| accent | نص | يحدد لون الإبراز لعنصر القائمة. تشمل الخيارات: `افتراضي`, `خطر`, `موضع مؤقت` |
| نص | نص | المحتوى النصي لعنصر القائمة |
| أزرار الأيقونة | array | مجموعة من الكائنات التي تمثل أيقونات إضافية مرتبطة بعنصر القائمة |
| isTooltipOpen | قيمة منطقية | يتحكم في ظهور تلميح الأدوات المرتبط بعنصر القائمة |
| معرف الفحص | نص | السمة data-testid لأغراض الاختبار |
| عند النقر | function | دالة الاستدعاء يتم تنشيطها عند النقر فوق عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لتصميم إضافي |
</Tab>
</Tabs>
## الأشكال
@@ -64,42 +72,49 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة على نمط الأوامر داخل القائمة للإشارة إلى اختصارات لوحة المفاتيح.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemCommand } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
const handleCommandClick = () => {
console.log("Command clicked!");
};
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemCommand } from "twenty-ui/display";
return (
<MenuItemCommand
LeftIcon={IconBell}
text="First Option"
firstHotKey="⌘"
secondHotKey="1"
isSelected={true}
onClick={handleCommandClick}
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
const handleCommandClick = () => {
console.log("Command clicked!");
};
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ----------------------------------------------------- |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية إذا ظهرت قبل النص في عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| firstHotKey | string | أول اختصار لوحة مفاتيح مرتبط بالأمر |
| secondHotKey | string | اختصار لوحة المفاتيح الثاني المرتبط بالأمر |
| isSelected | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددا أو مميزا |
| عند النقر | دالة | دالة الاستدعاء يتم تنشيطها عند النقر فوق عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لإضافة التنسيق |
</Tab>
return (
<MenuItemCommand
LeftIcon={IconBell}
text="First Option"
firstHotKey="⌘"
secondHotKey="1"
isSelected={true}
onClick={handleCommandClick}
className
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ----------------------------------------------------- |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية إذا ظهرت قبل النص في عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| firstHotKey | string | أول اختصار لوحة مفاتيح مرتبط بالأمر |
| secondHotKey | string | اختصار لوحة المفاتيح الثاني المرتبط بالأمر |
| isSelected | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددا أو مميزا |
| عند النقر | دالة | دالة الاستدعاء يتم تنشيطها عند النقر فوق عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لإضافة التنسيق |
</Tab>
</Tabs>
### قابلة للسحب
@@ -107,45 +122,52 @@ image: /images/user-guide/kanban-views/kanban.png
مكون عنصر قائمة قابل للسحب مصمم ليتم استخدامه في قائمة أو قائمة حيث يمكن سحب العناصر، ويتم تنفيذ إجراءات إضافية عبر أزرار الأيقونات.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { IconBell } from "@tabler/icons-react";
import { IconAlertCircle } from "@tabler/icons-react";
import { MenuItemDraggable } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
const handleMenuItemClick = (event) => {
console.log("Menu item clicked!", event);
};
```jsx
import { IconBell } from "@tabler/icons-react";
import { IconAlertCircle } from "@tabler/icons-react";
import { MenuItemDraggable } from "twenty-ui/display";
return (
<MenuItemDraggable
LeftIcon={IconBell}
accent="default"
iconButtons={[{ Icon: IconAlertCircle, onClick: handleButtonClick }]}
isTooltipOpen={false}
onClick={handleMenuItemClick}
text="Menu item draggable"
isDragDisabled={false}
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
const handleMenuItemClick = (event) => {
console.log("Menu item clicked!", event);
};
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------------------------------------- |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| accent | نص | لون العنصر لهجة القائمة. يمكن أن يكون إحدى القيم: `default`، `placeholder`، و`danger` |
| أزرار الأيقونات | array | مصفوفة الكائنات التي تمثل أزرار الأيقونات الإضافية المرتبطة بعنصر القائمة |
| isTooltipOpen | قيمة منطقية | يتحكم في ظهور تلميح الأدوات المرتبط بعنصر القائمة |
| عند_النقر | دالة | وظيفة استدعاء ليتم تشغيلها عند النقر فوق الرابط |
| نص | نص | محتوى النص لعنصر القائمة |
| isDragDisabled | قيمة منطقية | يشير إلى ما إذا كان تم تعطيل السحب |
| اسم الفئة | نص | اسم اختياري لإضافة التنسيق |
</Tab>
return (
<MenuItemDraggable
LeftIcon={IconBell}
accent="default"
iconButtons={[{ Icon: IconAlertCircle, onClick: handleButtonClick }]}
isTooltipOpen={false}
onClick={handleMenuItemClick}
text="Menu item draggable"
isDragDisabled={false}
className
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------------------------------------- |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| accent | نص | لون العنصر لهجة القائمة. يمكن أن يكون إحدى القيم: `default`، `placeholder`، و`danger` |
| أزرار الأيقونات | array | مصفوفة الكائنات التي تمثل أزرار الأيقونات الإضافية المرتبطة بعنصر القائمة |
| isTooltipOpen | قيمة منطقية | يتحكم في ظهور تلميح الأدوات المرتبط بعنصر القائمة |
| عند_النقر | دالة | وظيفة استدعاء ليتم تشغيلها عند النقر فوق الرابط |
| نص | نص | محتوى النص لعنصر القائمة |
| isDragDisabled | قيمة منطقية | يشير إلى ما إذا كان تم تعطيل السحب |
| اسم الفئة | نص | اسم اختياري لإضافة التنسيق |
</Tab>
</Tabs>
### التحديد المتعدد
@@ -153,34 +175,41 @@ image: /images/user-guide/kanban-views/kanban.png
يوفر طريقة لتنفيذ وظيفة التحديد المتعدد مع مربع اختيار مصاحب.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemMultiSelect } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemMultiSelect } from "twenty-ui/display";
return (
<MenuItemMultiSelect
LeftIcon={IconBell}
text="First Option"
selected={false}
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------ |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| نص | string | محتوى النص لعنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| onSelectChange | دالة | وظيفة استدعاء يتم تشغيلها عند تغيير حالة مربع الاختيار |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
return (
<MenuItemMultiSelect
LeftIcon={IconBell}
text="First Option"
selected={false}
className
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------ |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| نص | string | محتوى النص لعنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| onSelectChange | دالة | وظيفة استدعاء يتم تشغيلها عند تغيير حالة مربع الاختيار |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
</Tabs>
### التحديد المتعدد للصورة الرمزية
@@ -188,35 +217,42 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة متعدد الخيارات مع صورة رمزية، ومربع اختيار للتحديد، ومحتوى نصي.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { MenuItemMultiSelectAvatar } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
const imageUrl =
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAABSgAwAEAAAAAQAAABQAAAAA/8AAEQgAFAAUAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/dAAQAAv/aAAwDAQACEQMRAD8Ava1q728otYY98joSCTgZrnbXWdTtrhrfVZXWLafmcAEkdgR/hVltQku9Q8+OIEBcGOT+ID0PY1ka1KH2u8ToqnPLbmIqG7u6LtbQ7RXBRec4Uck9eKXcPWsKDWVnhWSL5kYcFelSf2m3901POh8jP//QoyIAnTuKpXsY82NsksUyWPU5q/L9z8RVK++/F/uCsVsaEURwgA4HtT9x9TUcf3KfUGh//9k=";
```jsx
import { MenuItemMultiSelectAvatar } from "twenty-ui/display";
return (
<MenuItemMultiSelectAvatar
avatar={<img src={imageUrl} alt="Avatar" />}
text="First Option"
selected={false}
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
const imageUrl =
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAABSgAwAEAAAAAQAAABQAAAAA/8AAEQgAFAAUAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/dAAQAAv/aAAwDAQACEQMRAD8Ava1q728otYY98joSCTgZrnbXWdTtrhrfVZXWLafmcAEkdgR/hVltQku9Q8+OIEBcGOT+ID0PY1ka1KH2u8ToqnPLbmIqG7u6LtbQ7RXBRec4Uck9eKXcPWsKDWVnhWSL5kYcFelSf2m3901POh8jP//QoyIAnTuKpXsY82NsksUyWPU5q/L9z8RVK++/F/uCsVsaEURwgA4HtT9x9TUcf3KfUGh//9k=";
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ----------- | -------------------------------------------------------------------- |
| الصورة الرمزبية | `ReactNode` | الصورة الرمزبية أو الأيقونة لعرضها على الجانب الأيسر من عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| onSelectChange | دالة | وظيفة استدعاء يتم تشغيلها عند تغيير حالة مربع الاختيار |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
return (
<MenuItemMultiSelectAvatar
avatar={<img src={imageUrl} alt="Avatar" />}
text="First Option"
selected={false}
className
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ----------- | -------------------------------------------------------------------- |
| الصورة الرمزبية | `ReactNode` | الصورة الرمزبية أو الأيقونة لعرضها على الجانب الأيسر من عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| onSelectChange | دالة | وظيفة استدعاء يتم تشغيلها عند تغيير حالة مربع الاختيار |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
</Tabs>
### التنقل
@@ -224,36 +260,43 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة يتضمن أيقونة يسارية اختيارية، محتوى نصي، وأيقونة سهم إلى اليمين.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemNavigate } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
const handleNavigation = () => {
console.log("Navigate to another page");
};
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemNavigate } from "twenty-ui/display";
return (
<MenuItemNavigate
LeftIcon={IconBell}
text="First Option"
onClick={handleNavigation}
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
const handleNavigation = () => {
console.log("Navigate to another page");
};
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------ |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| عند_النقر | دالة | وظيفة الاستدعاء يتم تنشيطها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
return (
<MenuItemNavigate
LeftIcon={IconBell}
text="First Option"
onClick={handleNavigation}
className
/>
);
};
```
</Tab>
<Tab title="العناصر">
| العناصر | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------ |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| عند_النقر | دالة | وظيفة الاستدعاء يتم تنشيطها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
</Tabs>
### اختيار
@@ -261,42 +304,49 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة يمكن تحديده، مع شكل محتوى متاح (أيقونة ونص) ومُؤشر (أيقونة تحقق) لحالة الاختيار.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemSelect } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
const handleSelection = () => {
console.log("Menu item selected");
};
```jsx
import { IconBell } from "@tabler/icons-react";
import { MenuItemSelect } from "twenty-ui/display";
return (
<MenuItemSelect
LeftIcon={IconBell}
text="First Option"
selected={true}
disabled={false}
hovered={false}
onClick={handleSelection}
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
const handleSelection = () => {
console.log("Menu item selected");
};
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ---------------------------------------------------------- |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة معطلا |
| معطل | قيمة منطقية | يشير إلى ما إذا كان يتم التحويم حاليًا على عنصر القائمة |
| عند_النقر | دالة | دالة الاستدعاء التي يتم تحفيزها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
return (
<MenuItemSelect
LeftIcon={IconBell}
text="First Option"
selected={true}
disabled={false}
hovered={false}
onClick={handleSelection}
className
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ---------------------------------------------------------- |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تظهر قبل النص في عنصر القائمة |
| نص | نص | محتوى النص لعنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة معطلا |
| معطل | قيمة منطقية | يشير إلى ما إذا كان يتم التحويم حاليًا على عنصر القائمة |
| عند_النقر | دالة | دالة الاستدعاء التي يتم تحفيزها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
</Tabs>
### اختيار الصورة الرمزية
@@ -304,47 +354,54 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة قابل للتحديد مع صورة رمزية، يتضمن محتوى يسارياً اختيارياً (صورة رمزية ونصاً) ومؤشراً (أيقونة التحقق) لحالة التحديد.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { MenuItemSelectAvatar } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
const imageUrl =
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAABSgAwAEAAAAAQAAABQAAAAA/8AAEQgAFAAUAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6OnqvLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/dAAQAAv/aAAwDAQACEQMRAD8Ava1q728otYY98joSCTgZrnbXWdTtrhrfVZXWLafmcAEkdgR/hVltQku9Q8+OIEBcGOT+ID0PY1ka1KH2u8ToqnPLbmIqG7u6LtbQ7RXBRec4Uck9eKXcPWsKDWVnhWSL5kYcFelSf2m3901POh8jP//QoyIAnTuKpXsY82NsksUyWPU5q/L9z8RVK++/F/uCsVsaEURwgA4HtT9x9TUcf3KfUGh//9k=";
```jsx
import { MenuItemSelectAvatar } from "twenty-ui/display";
const handleSelection = () => {
console.log("تم تحديد عنصر القائمة");
};
export const MyComponent = () => {
const imageUrl =
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAABSgAwAEAAAAAQAAABQAAAAA/8AAEQgAFAAUAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6OnqvLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMACwgICggHCwoJCg0MCw0RHBIRDw8RIhkaFBwpJCsqKCQnJy0yQDctMD0wJyc4TDk9Q0VISUgrNk9VTkZUQEdIRf/bAEMBDA0NEQ8RIRISIUUuJy5FRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRf/dAAQAAv/aAAwDAQACEQMRAD8Ava1q728otYY98joSCTgZrnbXWdTtrhrfVZXWLafmcAEkdgR/hVltQku9Q8+OIEBcGOT+ID0PY1ka1KH2u8ToqnPLbmIqG7u6LtbQ7RXBRec4Uck9eKXcPWsKDWVnhWSL5kYcFelSf2m3901POh8jP//QoyIAnTuKpXsY82NsksUyWPU5q/L9z8RVK++/F/uCsVsaEURwgA4HtT9x9TUcf3KfUGh//9k=";
return (
<MenuItemSelectAvatar
avatar={<img src={imageUrl} alt="الصورة الرمزية" />}
text="الخيار الأول"
selected={true}
disabled={false}
hovered={false}
testId="menu-item-test"
onClick={handleSelection}
className
/>
);
};
const handleSelection = () => {
console.log("تم تحديد عنصر القائمة");
};
```
</Tab>
return (
<MenuItemSelectAvatar
avatar={<img src={imageUrl} alt="الصورة الرمزية" />}
text="الخيار الأول"
selected={true}
disabled={false}
hovered={false}
testId="menu-item-test"
onClick={handleSelection}
className
/>
);
};
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| -------------- | ------------ | ---------------------------------------------------------------------------- |
| الصورة الرمزية | `مكون React` | الصورة الرمزية أو الأيقونة التي سيتم عرضها على الجانب الأيسر من عنصر القائمة |
| نص | نص | محتوى النص في عنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة معطلاً |
| معلق عليه | قيمة منطقية | يشير إلى ما إذا كان يتم التحويم حاليًا على عنصر القائمة |
| testId | نص | سمة data-testid لأغراض الاختبار |
| عند_النقر | دالة | دالة الاستدعاء التي يتم تحفيزها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري للتنسيق الإضافي. |
</Tab>
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| -------------- | ------------ | ---------------------------------------------------------------------------- |
| الصورة الرمزية | `مكون React` | الصورة الرمزية أو الأيقونة التي سيتم عرضها على الجانب الأيسر من عنصر القائمة |
| نص | نص | محتوى النص في عنصر القائمة |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة معطلاً |
| معلق عليه | قيمة منطقية | يشير إلى ما إذا كان يتم التحويم حاليًا على عنصر القائمة |
| testId | نص | سمة data-testid لأغراض الاختبار |
| عند_النقر | دالة | دالة الاستدعاء التي يتم تحفيزها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري للتنسيق الإضافي. |
</Tab>
</Tabs>
### اختيار اللون
@@ -352,41 +409,48 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة قابل للتحديد يتضمن عينة لون للسيناريوهات التي تريد فيها من المستخدمين اختيار لون من القائمة.
<Tabs>
<Tab title="الاستخدام">
```jsx
import { MenuItemSelectColor } from "twenty-ui/display";
<Tab title="الاستخدام">
export const MyComponent = () => {
const handleSelection = () => {
console.log("Menu item selected");
};
```jsx
import { MenuItemSelectColor } from "twenty-ui/display";
return (
<MenuItemSelectColor
color="green"
selected={true}
disabled={false}
hovered={true}
variant="default"
onClick={handleSelection}
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
const handleSelection = () => {
console.log("Menu item selected");
};
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| اللون | نص | لون الثيم المعروض كعينة في عنصر القائمة. الخيارات تشمل: `أخضر`, `تركواز`, `سماوي`, `أزرق`, `أرجواني`, `وردي`, `أحمر`, `برتقالي`, `أصفر`, `رمادي`. |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة معطلاً |
| معلق عليه | قيمة منطقية | يشير إلى ما إذا كان يتم التحويم حاليًا على عنصر القائمة |
| البديل | نص | المتغير لعينة اللون. يمكن أن يكون إما `افتراضي` أو `خط أنابيب` |
| عند_النقر | دالة | دالة الاستدعاء التي يتم تحفيزها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري للتنسيق الإضافي. |
</Tab>
return (
<MenuItemSelectColor
color="green"
selected={true}
disabled={false}
hovered={true}
variant="default"
onClick={handleSelection}
className
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| اللون | نص | لون الثيم المعروض كعينة في عنصر القائمة. الخيارات تشمل: `أخضر`, `تركواز`, `سماوي`, `أزرق`, `أرجواني`, `وردي`, `أحمر`, `برتقالي`, `أصفر`, `رمادي`. |
| المحدد | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة محددًا (مفحوص) |
| تعطيل | قيمة منطقية | يشير إلى ما إذا كان عنصر القائمة معطلاً |
| معلق عليه | قيمة منطقية | يشير إلى ما إذا كان يتم التحويم حاليًا على عنصر القائمة |
| البديل | نص | المتغير لعينة اللون. يمكن أن يكون إما `افتراضي` أو `خط أنابيب` |
| عند_النقر | دالة | دالة الاستدعاء التي يتم تحفيزها عند النقر على عنصر القائمة |
| اسم الفئة | نص | اسم اختياري للتنسيق الإضافي. |
</Tab>
</Tabs>
### تبديل
@@ -394,35 +458,42 @@ image: /images/user-guide/kanban-views/kanban.png
عنصر قائمة مع مفتاح تبديل مرتبط للسماح للمستخدمين بتمكين أو تعطيل ميزة معينة
<Tabs>
<Tab title="الاستخدام">
```jsx
import { IconBell } from '@tabler/icons-react';
<Tab title="الاستخدام">
import { MenuItemToggle } from 'twenty-ui/display';
```jsx
import { IconBell } from '@tabler/icons-react';
export const MyComponent = () => {
import { MenuItemToggle } from 'twenty-ui/display';
return (
<MenuItemToggle
LeftIcon={IconBell}
text="First Option"
toggled={true}
toggleSize="small"
className
/>
);
};
```
</Tab>
export const MyComponent = () => {
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------------ |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تُعرض قبل النص في عنصر القائمة |
| نص | نص | محتوى النص في عنصر القائمة |
| مبدل | قيمة منطقية | يشير إلى ما إذا كان مفتاح التبديل في حالة "تشغيل" أو "إيقاف" |
| onToggleChange | دالة | دالة الاستدعاء التي يتم تحفيزها عند تغيير حالة مفتاح التبديل |
| حجم التبديل | نص | حجم مفتاح التبديل. يمكن أن يكون إما \ |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
return (
<MenuItemToggle
LeftIcon={IconBell}
text="First Option"
toggled={true}
toggleSize="small"
className
/>
);
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف |
| --------------- | ------------- | ------------------------------------------------------------ |
| الأيقونة اليسرى | مكون الأيقونة | أيقونة اختيارية تُعرض قبل النص في عنصر القائمة |
| نص | نص | محتوى النص في عنصر القائمة |
| مبدل | قيمة منطقية | يشير إلى ما إذا كان مفتاح التبديل في حالة "تشغيل" أو "إيقاف" |
| onToggleChange | دالة | دالة الاستدعاء التي يتم تحفيزها عند تغيير حالة مفتاح التبديل |
| حجم التبديل | نص | حجم مفتاح التبديل. يمكن أن يكون إما \ |
| اسم الفئة | نص | اسم اختياري لتنسيقات إضافية |
</Tab>
</Tabs>
@@ -10,40 +10,47 @@ image: /images/user-guide/table-views/table.png
يستعرض شريط التنقل الذي يحتوي على عدة مكونات `NavigationBarItem`.
<Tabs>
<Tab title="استخدام">
```jsx
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
<Tab title="استخدام">
export const MyComponent = () => {
```jsx
import { IconHome, IconUser, IconSettings } from '@tabler/icons-react';
import { NavigationBar } from "@/ui/navigation/navigation-bar/components/NavigationBar";
const navigationItems = [
{
name: "Home",
Icon: IconHome,
onClick: () => console.log("Home clicked"),
},
{
name: "Profile",
Icon: IconUser,
onClick: () => console.log("Profile clicked"),
},
{
name: "Settings",
Icon: IconSettings,
onClick: () => console.log("Settings clicked"),
},
];
export const MyComponent = () => {
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
};
```
</Tab>
const navigationItems = [
{
name: "Home",
Icon: IconHome,
onClick: () => console.log("Home clicked"),
},
{
name: "Profile",
Icon: IconUser,
onClick: () => console.log("Profile clicked"),
},
{
name: "Settings",
Icon: IconSettings,
onClick: () => console.log("Settings clicked"),
},
];
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ---------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| اسم العنصر النشط | نص | اسم العنصر النشط حاليًا في قائمة التنقل |
| العناصر | مصفوفة | مصفوفة من الكائنات التي تمثل كل عنصر من عناصر التنقل. كل كائن يحتوي على `الاسم` للعنصر، ومكون `الأيقونة` للعرض، ودالة `onClick` التي تستدعي عند النقر على العنصر |
</Tab>
return <NavigationBar activeItemName="Home" items={navigationItems}/>;
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ---------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| اسم العنصر النشط | نص | اسم العنصر النشط حاليًا في قائمة التنقل |
| العناصر | مصفوفة | مصفوفة من الكائنات التي تمثل كل عنصر من عناصر التنقل. كل كائن يحتوي على `الاسم` للعنصر، ومكون `الأيقونة` للعرض، ودالة `onClick` التي تستدعي عند النقر على العنصر |
</Tab>
</Tabs>
@@ -10,25 +10,33 @@ image: /images/user-guide/api/api.png
يعرض التقدم من خلال سلسلة من الخطوات المرقمة عن طريق تمييز الخطوة النشطة. يولد حاوية تحتوي على خطوات، يتم تمثيل كل منها بواسطة مكون 'Step'.
<Tabs>
<Tab title="استخدام">
```jsx
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
<Tab title="استخدام">
export const MyComponent = () => {
return (
<StepBar activeStep={2}>
<StepBar.Step>Step 1</StepBar.Step>
<StepBar.Step>Step 2</StepBar.Step>
<StepBar.Step>Step 3</StepBar.Step>
</StepBar>
);
};
```
</Tab>
```jsx
import { StepBar } from "@/ui/navigation/step-bar/components/StepBar";
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------- | ----- | ----------------------------------------------------------------- |
| الخطوة النشطة | رقم | مؤشر للخطوة النشطة حاليًا. هذا يحدد أي خطوة يجب إبرازها بشكل مرئي |
</Tab>
export const MyComponent = () => {
return (
<StepBar activeStep={2}>
<StepBar.Step>Step 1</StepBar.Step>
<StepBar.Step>Step 2</StepBar.Step>
<StepBar.Step>Step 3</StepBar.Step>
</StepBar>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف |
| ------------- | ----- | ----------------------------------------------------------------- |
| الخطوة النشطة | رقم | مؤشر للخطوة النشطة حاليًا. هذا يحدد أي خطوة يجب إبرازها بشكل مرئي |
</Tab>
</Tabs>
@@ -10,35 +10,43 @@ image: /images/user-guide/emails/emails_header.png
يشير إلى تقدم أو عد تنازلي ويتحرك من اليمين إلى اليسار.
<Tabs>
<Tab title="استخدام">
```jsx
import { ProgressBar } from "twenty-ui/feedback";
export const MyComponent = () => {
return (
<ProgressBar
duration={6000}
delay={0}
easing="easeInOut"
barHeight={10}
barColor="#4bb543"
autoStart={true}
/>
);
};
```
</Tab>
<Tab title="استخدام">
<Tab title="المحددات">
| المحددات | النوع | الوصف | الإعداد الافتراضي |
| ---------------- | ----------- | ------------------------------------------------------------------------------- | ----------------- |
| المدة | رقم | إجمالي مدة الرسوم المتحركة لشريط التقدم بالميلي ثانية | 3 |
| التأخير | رقم | التأخير في بدء الرسوم المتحركة لشريط التقدم بالميلي ثانية | 0 |
| التخفيف | نص | وظيفة التخفيف للرسوم المتحركة لشريط التقدم | easeInOut |
| ارتفاع الشريط | رقم | ارتفاع الشريط بالبكسل | 24 |
| لون الشريط | نص | لون الشريط | gray80 |
| التشغيل التلقائي | قيمة منطقية | إذا كان `true`، فإن الرسوم المتحركة لشريط التقدم تبدأ تلقائيًا عند تحميل المكون | `صحيح` |
</Tab>
```jsx
import { ProgressBar } from "twenty-ui/feedback";
export const MyComponent = () => {
return (
<ProgressBar
duration={6000}
delay={0}
easing="easeInOut"
barHeight={10}
barColor="#4bb543"
autoStart={true}
/>
);
};
```
</Tab>
<Tab title="المحددات">
| المحددات | النوع | الوصف | الإعداد الافتراضي |
| ---------------- | ----------- | ------------------------------------------------------------------------------- | ----------------- |
| المدة | رقم | إجمالي مدة الرسوم المتحركة لشريط التقدم بالميلي ثانية | 3 |
| التأخير | رقم | التأخير في بدء الرسوم المتحركة لشريط التقدم بالميلي ثانية | 0 |
| التخفيف | نص | وظيفة التخفيف للرسوم المتحركة لشريط التقدم | easeInOut |
| ارتفاع الشريط | رقم | ارتفاع الشريط بالبكسل | 24 |
| لون الشريط | نص | لون الشريط | gray80 |
| التشغيل التلقائي | قيمة منطقية | إذا كان `true`، فإن الرسوم المتحركة لشريط التقدم تبدأ تلقائيًا عند تحميل المكون | `صحيح` |
</Tab>
</Tabs>
## شريط التقدم الدائري
@@ -46,21 +54,30 @@ image: /images/user-guide/emails/emails_header.png
يشير إلى تقدم المهمة، ويستخدم غالباً في شاشات التحميل أو الأماكن التي ترغب فيها في إبلاغ العمليات الجارية إلى المستخدم.
<Tabs>
<Tab title="استخدام">
```jsx
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
export const MyComponent = () => {
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
};
```
</Tab>
<Tab title="استخدام">
```jsx
import { CircularProgressBar } from "@/ui/feedback/progress-bar/components/CircularProgressBar";
export const MyComponent = () => {
return <CircularProgressBar size={80} barWidth={6} barColor="green" />;
};
```
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف | الإعداد الافتراضي |
| ---------- | ----- | ----------------------- | ----------------- |
| الحجم | رقم | حجم شريط التقدم الدائري | 50 |
| عرض الشريط | رقم | عرض خط شريط التقدم | 5 |
| لون الشريط | نص | لون شريط التقدم | currentColor |
</Tab>
<Tab title="الخصائص">
| الخصائص | النوع | الوصف | الإعداد الافتراضي |
| ---------- | ----- | ----------------------- | ----------------- |
| الحجم | رقم | حجم شريط التقدم الدائري | 50 |
| عرض الشريط | رقم | عرض خط شريط التقدم | 5 |
| لون الشريط | نص | لون شريط التقدم | currentColor |
</Tab>
</Tabs>
@@ -4,7 +4,7 @@ description: ادمج قدرات الذكاء الاصطناعي مباشرةً
---
<Note>
هذه الميزة قيد التطوير حالياً وستكون متاحة في النسخة التجريبية قريباً.
هذه الميزة قيد التطوير حالياً وستكون متاحة في النسخة التجريبية قريباً.
</Note>
## نظرة عامة
@@ -4,7 +4,7 @@ description: مساعد ذكي يساعدك على التفاعل مع بيان
---
<Note>
هذه الميزة قيد التطوير حالياً وستكون متاحة في النسخة التجريبية قريباً.
هذه الميزة قيد التطوير حالياً وستكون متاحة في النسخة التجريبية قريباً.
</Note>
## نظرة عامة
@@ -26,7 +26,7 @@ description: تحكّم بما يمكن لوكلاء الذكاء الاصطنا
| **إمكانية التدقيق** | تتبُّع الإجراءات التي نفّذها كل وكيل |
<Note>
بالنسبة لوكلاء الذكاء الاصطناعي الذين يعملون ضمن سير العمل، يضمن تعيين الدور ألا يتمكّن الوكيل من الوصول إلى البيانات أو تعديلها خارج نطاقه المقصود — حتى إذا كانت لسير العمل أذونات أوسع.
بالنسبة لوكلاء الذكاء الاصطناعي الذين يعملون ضمن سير العمل، يضمن تعيين الدور ألا يتمكّن الوكيل من الوصول إلى البيانات أو تعديلها خارج نطاقه المقصود — حتى إذا كانت لسير العمل أذونات أوسع.
</Note>
## ذات صلة
@@ -4,6 +4,7 @@ description: الأسئلة الشائعة حول ميزات الذكاء الا
---
<AccordionGroup>
<Accordion title="متى ستكون ميزات الذكاء الاصطناعي متاحة؟">
ميزات الذكاء الاصطناعي قيد التطوير حالياً وسيتم إصدارها كإصدار تجريبي قريباً. ترقبوا التحديثات!
</Accordion>
@@ -26,4 +27,5 @@ description: الأسئلة الشائعة حول ميزات الذكاء الا
<Accordion title="هل يمكنني استخدام نماذج الذكاء الاصطناعي الخاصة بي؟">
في البداية، ستستخدم Twenty نماذج ذكاء اصطناعي مدمجة. قد تتم إضافة دعم لنماذج الذكاء الاصطناعي المخصصة أو الخارجية في إصدارات مستقبلية استناداً إلى ملاحظات المستخدمين.
</Accordion>
</AccordionGroup>
@@ -19,7 +19,7 @@ description: تعرّف على خطط تسعير Twenty وكيفية التبد
* دعم قياسي
<Note>
الميزات المتميزة (SSO وأذونات على مستوى الصف) غير مشمولة في خطة Pro.
الميزات المتميزة (SSO وأذونات على مستوى الصف) غير مشمولة في خطة Pro.
</Note>
### المؤسسة (سحابي)
@@ -17,7 +17,7 @@ description: فهم أرصدة سير العمل، والاستهلاك، وكي
| سنوي | 50 مليون/سنة |
<Note>
تم تصميم 5 ملايين رصيد شهريًا لتمكينك من تشغيل الأتمتة دون القلق بشأن التكاليف. بالنسبة إلى معظم سير العمل التي تستخدم الإجراءات القياسية، فهذا أكثر من كافٍ. ستحتاج إلى أرصدة إضافية فقط عند تشغيل عقد برمجية متقدمة أو ميزات مدعومة بالذكاء الاصطناعي.
تم تصميم 5 ملايين رصيد شهريًا لتمكينك من تشغيل الأتمتة دون القلق بشأن التكاليف. بالنسبة إلى معظم سير العمل التي تستخدم الإجراءات القياسية، فهذا أكثر من كافٍ. ستحتاج إلى أرصدة إضافية فقط عند تشغيل عقد برمجية متقدمة أو ميزات مدعومة بالذكاء الاصطناعي.
</Note>
## استهلاك الأرصدة
@@ -6,81 +6,78 @@ description: الأسئلة الشائعة حول تسعير Twenty والفوت
## التسعير
<AccordionGroup>
<Accordion title="هل يمكنني استخدام Twenty مجانًا أثناء الاستضافة الذاتية؟">
نعم، يمكنك استخدام Twenty مجانًا أثناء الاستضافة الذاتية. ستحصل على إمكانية الوصول إلى كل ما هو مضمّن في خطة Pro (السحابة)، باستثناء الدعم من فريقنا الأساسي. الدعم متاح عبر مجتمعنا في Discord.
<Accordion title="هل يمكنني استخدام Twenty مجانًا أثناء الاستضافة الذاتية؟">
نعم، يمكنك استخدام Twenty مجانًا أثناء الاستضافة الذاتية. ستحصل على إمكانية الوصول إلى كل ما هو مضمّن في خطة Pro (السحابة)، باستثناء الدعم من فريقنا الأساسي. الدعم متاح عبر مجتمعنا في Discord.
إذا كنت تريد الاستضافة الذاتية وتحتاج إلى الميزات المتميزة (SSO وأذونات على مستوى السجل)، فيمكنك اختيار ترخيص Organization (الاستضافة الذاتية) المدفوع. يشمل ذلك أيضًا دعمًا من فريق Twenty ويلغي شرط نشر الشيفرة المخصصة كمصدر مفتوح قبل التوزيع.
</Accordion>
إذا كنت تريد الاستضافة الذاتية وتحتاج إلى الميزات المتميزة (SSO وأذونات على مستوى السجل)، فيمكنك اختيار ترخيص Organization (الاستضافة الذاتية) المدفوع. يشمل ذلك أيضًا دعمًا من فريق Twenty ويلغي شرط نشر الشيفرة المخصصة كمصدر مفتوح قبل التوزيع.
</Accordion>
<Accordion title="ما هي الميزات المتميزة؟">
لا تتوفر الميزات المتميزة إلا في خطط Organization (السحابة أو الاستضافة الذاتية):
<Accordion title="ما هي الميزات المتميزة؟">
لا تتوفر الميزات المتميزة إلا في خطط Organization (السحابة أو الاستضافة الذاتية):
* **تكامل SSO**: تسجيل الدخول الأحادي مع موفر الهوية لديك
* **أذونات على مستوى السجل**: تحكم دقيق في الوصول على مستوى السجل
</Accordion>
* **تكامل SSO**: تسجيل الدخول الأحادي مع موفر الهوية لديك
* **أذونات على مستوى السجل**: تحكم دقيق في الوصول على مستوى السجل
</Accordion>
<Accordion title="هل تقدمون مقاعد مجانية للمستخدمين العارضين فقط؟">
نحن لا نقدم مقاعد مجانية. التسعير يكون لكل مستخدم وكل مستخدم يحتاج إلى ترخيص للوصول إلى Twenty.
</Accordion>
<Accordion title="هل تقدمون مقاعد مجانية للمستخدمين العارضين فقط؟">
نحن لا نقدم مقاعد مجانية. التسعير يكون لكل مستخدم وكل مستخدم يحتاج إلى ترخيص للوصول إلى Twenty.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى خطة المؤسسة؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `التبديل إلى المؤسسة`.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى خطة المؤسسة؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `التبديل إلى المؤسسة`.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى الخطة الاحترافية؟">
يرجى التواصل مع فريقنا مباشرة عبر الدعم، لا يوجد حاليًا طريقة سهلة للقيام بذلك عبر واجهة المستخدم.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى الخطة الاحترافية؟">
يرجى التواصل مع فريقنا مباشرة عبر الدعم، لا يوجد حاليًا طريقة سهلة للقيام بذلك عبر واجهة المستخدم.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى سنوي؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `التبديل إلى السنوي`.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى سنوي؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `التبديل إلى السنوي`.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى شهري؟">
يرجى التواصل مع فريقنا مباشرة عبر الدعم، لا يوجد حاليًا طريقة سهلة للقيام بذلك عبر واجهة المستخدم.
</Accordion>
<Accordion title="أين يمكنني تبديل اشتراكي إلى شهري؟">
يرجى التواصل مع فريقنا مباشرة عبر الدعم، لا يوجد حاليًا طريقة سهلة للقيام بذلك عبر واجهة المستخدم.
</Accordion>
<Accordion title="أين يمكنني رؤية استخدام الرصيد الخاص بي؟">
ستجد ذلك ضمن `الإعدادات → الفوترة`.
</Accordion>
<Accordion title="أين يمكنني رؤية استخدام الرصيد الخاص بي؟">
ستجد ذلك ضمن `الإعدادات → الفوترة`.
</Accordion>
<Accordion title="كم عدد أرصدة سير العمل لدينا؟">
يعتمد عدد الأرصدة على دورة الفوترة لديك، وليس على خطتك:
* **الاشتراكات الشهرية**: 5 ملايين رصيد شهريًا
* **الاشتراكات السنوية**: 50 مليون رصيد سنويًا
</Accordion>
<Accordion title="كم عدد أرصدة سير العمل لدينا؟">
يعتمد عدد الأرصدة على دورة الفوترة لديك، وليس على خطتك:
<Accordion title="كيف يعمل استهلاك رصيد سير العمل؟">
يستهلك كل إجراء سير عمل الرصيد بناءً على تعقيده.
* **العمليات الداخلية الأساسية** (مثل البحث والتحديث وإنشاء السجلات) تستهلك عدد قليل من الأرصدة
* **عمليات أكثر تعقيدًا** مثل عقد الشيفرة والطلبات إلى الخدمات الخارجية تستهلك مزيدًا من الأرصدة
* **طلبات الذكاء الاصطناعي** (قريبًا!) ستستهلك أيضًا المزيد من الأرصدة بناءً على الاستخدام
* **الاشتراكات الشهرية**: 5 ملايين رصيد شهريًا
* **الاشتراكات السنوية**: 50 مليون رصيد سنويًا
</Accordion>
يتم خصم الأرصدة فورًا عند تنفيذ سير العمل. يمكنك متابعة استخدامك في **الإعدادات → الفوترة** لمتابعة الاستهلاك والأرصدة المتبقية.
</Accordion>
<Accordion title="كيف يعمل استهلاك رصيد سير العمل؟">
يستهلك كل إجراء سير عمل الرصيد بناءً على تعقيده.
* **العمليات الداخلية الأساسية** (مثل البحث والتحديث وإنشاء السجلات) تستهلك عدد قليل من الأرصدة
* **عمليات أكثر تعقيدًا** مثل عقد الشيفرة والطلبات إلى الخدمات الخارجية تستهلك مزيدًا من الأرصدة
* **طلبات الذكاء الاصطناعي** (قريبًا!) ستستهلك أيضًا المزيد من الأرصدة بناءً على الاستخدام
يتم خصم الأرصدة فورًا عند تنفيذ سير العمل. يمكنك متابعة استخدامك في **الإعدادات → الفوترة** لمتابعة الاستهلاك والأرصدة المتبقية.
</Accordion>
<Accordion title="هل يمكنني شراء المزيد من أرصدة سير العمل؟">
تستطيع شراء أرصدة إضافية ضمن `الإعدادات → الفوترة`.
</Accordion>
<Accordion title="هل يمكنني شراء المزيد من أرصدة سير العمل؟">
تستطيع شراء أرصدة إضافية ضمن `الإعدادات → الفوترة`.
</Accordion>
</AccordionGroup>
## الفوترة
<AccordionGroup>
<Accordion title="أين يمكنني إدارة تفاصيل الفوترة؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`.
</Accordion>
<Accordion title="أين يمكنني إدارة تفاصيل الفوترة؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`.
</Accordion>
<Accordion title="أين يمكنني تحديث طريقة الدفع الخاصة بي؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `عرض تفاصيل الفوترة`. ستتمكن من إضافة طريقة دفع جديدة هناك.
</Accordion>
<Accordion title="أين يمكنني تحديث طريقة الدفع الخاصة بي؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `عرض تفاصيل الفوترة`. ستتمكن من إضافة طريقة دفع جديدة هناك.
</Accordion>
<Accordion title="أين يمكنني تحديث معلومات الفوترة الخاصة بي؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `عرض تفاصيل الفوترة`. ستتمكن من تعديل معلومات الفوترة هناك.
</Accordion>
<Accordion title="أين يمكنني تحديث معلومات الفوترة الخاصة بي؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `عرض تفاصيل الفوترة`. ستتمكن من تعديل معلومات الفوترة هناك.
</Accordion>
<Accordion title="أين يمكنني الوصول إلى فواتيري؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `عرض تفاصيل الفوترة`. سترى جميع فواتيرك في أسفل الشاشة.
</Accordion>
<Accordion title="أين يمكنني الوصول إلى فواتيري؟">
يمكنك القيام بذلك ضمن `الإعدادات → الفوترة`. ثم انقر على `عرض تفاصيل الفوترة`. سترى جميع فواتيرك في أسفل الشاشة.
</Accordion>
</AccordionGroup>
@@ -16,7 +16,6 @@ image: /images/user-guide/setup/pricing.png
<Card title="خطط التسعير" icon="tags" href="/l/ar/user-guide/billing/capabilities/pricing-plans">
تعرّف على خطط تسعير Twenty وما تتضمنه.
</Card>
<Card title="الأسئلة الشائعة" icon="circle-question" href="/l/ar/user-guide/billing/how-tos/billing-faq">
أسئلة شائعة حول التسعير والفوترة.
</Card>
@@ -34,7 +34,7 @@ description: معلومات حول إرسال رسائل البريد الإلك
بالنسبة لتسلسلات البريد الإلكتروني والنشرات الإخبارية، نوصي باستخدام سير العمل لربط Twenty بأداة مخصّصة للتسويق عبر البريد الإلكتروني.
<Warning>
لا ينبغي إرسال رسائل بريد إلكتروني جماعية مباشرةً من صندوق بريدك لحماية سمعة نطاقك. استخدم أداة مخصّصة للاتصالات الجماعية.
لا ينبغي إرسال رسائل بريد إلكتروني جماعية مباشرةً من صندوق بريدك لحماية سمعة نطاقك. استخدم أداة مخصّصة للاتصالات الجماعية.
</Warning>
→ اطّلع على [كيفية إرسال الرسائل عبر سير العمل](/l/ar/user-guide/workflows/capabilities/send-emails-from-workflows) للحصول على إرشادات الإعداد
@@ -46,7 +46,7 @@ image: /images/user-guide/emails/emails_header.png
* **إعدادات المزامنة**: تكوين إعدادات مختلفة لكل صندوق بريد
<Warning>
لا يمكن ربط إلا صناديق البريد الحقيقية (مثل support@domain.com بصندوق الوارد الخاص بها). لا يمكن ربط الأسماء المستعارة للبريد الإلكتروني التي توجه إلى صندوق بريد آخر بـ Twenty.
لا يمكن ربط إلا صناديق البريد الحقيقية (مثل support@domain.com بصندوق الوارد الخاص بها). لا يمكن ربط الأسماء المستعارة للبريد الإلكتروني التي توجه إلى صندوق بريد آخر بـ Twenty.
</Warning>
## تكوين البريد الإلكتروني
@@ -121,7 +121,7 @@ image: /images/user-guide/emails/emails_header.png
**التحديثات كل 5 دقائق**: تتم مزامنة البيانات البريدية والتقويمية تلقائيًا كل 5 دقائق بعد الاستيراد الأولي.
<Note>
**توقيت المزامنة الأولية**: يكتمل تزامن التقويم بسرعة (عادةً خلال دقائق)، بينما تستغرق مزامنة البريد الإلكتروني وقتاً أطول لصناديق البريد الكبيرة. يقوم Twenty باستيراد رسائل البريد الإلكتروني بمعدل يقارب **400 رسالة في الدقيقة** (محدود بتقييد المعدل من Gmail API)، لذا فإن صندوق بريد يحتوي على 10,000 رسالة يستغرق حوالي 25 دقيقة. Don't worry if you see contacts from calendar events appearing before your email contacts; this is normal behavior.
**توقيت المزامنة الأولية**: يكتمل تزامن التقويم بسرعة (عادةً خلال دقائق)، بينما تستغرق مزامنة البريد الإلكتروني وقتاً أطول لصناديق البريد الكبيرة. يقوم Twenty باستيراد رسائل البريد الإلكتروني بمعدل يقارب **400 رسالة في الدقيقة** (محدود بتقييد المعدل من Gmail API)، لذا فإن صندوق بريد يحتوي على 10,000 رسالة يستغرق حوالي 25 دقيقة. Don't worry if you see contacts from calendar events appearing before your email contacts; this is normal behavior.
</Note>
## الخطوات التالية
@@ -23,7 +23,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
* **النمط**: الألوان، التسميات، والخيارات المرئية
<Note>
تظهر بعض الإعدادات فقط عند تهيئة إعدادات أخرى. على سبيل المثال، يظهر خيار **دقة التاريخ** فقط عند تحديد حقل تاريخ.
تظهر بعض الإعدادات فقط عند تهيئة إعدادات أخرى. على سبيل المثال، يظهر خيار **دقة التاريخ** فقط عند تحديد حقل تاريخ.
</Note>
---
@@ -58,11 +58,11 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
<img src="/images/user-guide/dashboard/dashboard-chart-settings-filters.png" alt="تصفية" />
<Tip>
بالنسبة لحقول التاريخ، جرّب **نسبي** لإنشاء عوامل تصفية ديناميكية مثل "آخر 7 أيام" أو "هذا الشهر" التي تتحدّث تلقائيًا.
بالنسبة لحقول التاريخ، جرّب **نسبي** لإنشاء عوامل تصفية ديناميكية مثل "آخر 7 أيام" أو "هذا الشهر" التي تتحدّث تلقائيًا.
</Tip>
<Tip>
تُدعم عوامل التصفية المتقدمة—أضِف قواعد تصفية متعددة وادمجها بمنطق **و** أو **أو**.
تُدعم عوامل التصفية المتقدمة—أضِف قواعد تصفية متعددة وادمجها بمنطق **و** أو **أو**.
</Tip>
---
@@ -110,11 +110,11 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
**نصيحة**: لمراحل خط الأنابيب، استخدم **الموضع** لعرض المراحل بترتيب عملية المبيعات لديك (Lead → Qualified → Proposal → Won).
<Note>
تعتمد الخيارات المعروضة على نوع الحقل لديك. حقول الاختيار تتضمن جميع الخيارات؛ حقول التاريخ والنص تعرض خيارات أقل.
تعتمد الخيارات المعروضة على نوع الحقل لديك. حقول الاختيار تتضمن جميع الخيارات؛ حقول التاريخ والنص تعرض خيارات أقل.
</Note>
<Tip>
مع الفرز **اليدوي**، اسحب الفئات لإنشاء ترتيب مخصص—مناسب تمامًا لإبراز شرائح محددة أو سرد قصة ببياناتك.
مع الفرز **اليدوي**، اسحب الفئات لإنشاء ترتيب مخصص—مناسب تمامًا لإبراز شرائح محددة أو سرد قصة ببياناتك.
</Tip>
### <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}> <ChartIcon icon="eye-off" />تجاهل القيم الصفرية </span>
@@ -147,7 +147,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
<img src="/images/user-guide/dashboard/dashboard-chart-settings-y-data-on-display.png" alt="البيانات المعروضة" />
<Note>
تعتمد الخيارات المعروضة على نوع الحقل ونوع المخطط. إذا لم تر العملية المتوقعة، فتحقّق من نوع الحقل.
تعتمد الخيارات المعروضة على نوع الحقل ونوع المخطط. إذا لم تر العملية المتوقعة، فتحقّق من نوع الحقل.
</Note>
### النسبة
@@ -222,7 +222,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
عيّن نظام الألوان لمخططك. اختر من مجموعة متنوعة من لوحات الألوان.
<Note>
عند التجميع حسب حقل اختيار، يستخدم المخطط تلقائيًا الألوان المحددة لكل خيار في إعدادات الحقل لديك.
عند التجميع حسب حقل اختيار، يستخدم المخطط تلقائيًا الألوان المحددة لكل خيار في إعدادات الحقل لديك.
</Note>
### <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}> <ChartIcon icon="gizmo" />اسم المحور </span>
@@ -256,7 +256,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
* **إيقاف**: لا تُعرض وسيلة إيضاح
<Tip>
**مرّر المؤشر** فوق عنصر في وسيلة الإيضاح لإبراز تلك المجموعة. **انقر** لإخفائه مؤقتًا من المخطط.
**مرّر المؤشر** فوق عنصر في وسيلة الإيضاح لإبراز تلك المجموعة. **انقر** لإخفائه مؤقتًا من المخطط.
</Tip>
---
@@ -84,9 +84,9 @@ description: أنشئ ونظّم لوحات المعلومات باستخدام
تعرض لوحات المعلومات حاليًا البيانات استنادًا إلى المنطقة الزمنية للمستخدم الذي يشاهدها. يعني هذا أن لوحة المعلومات نفسها قد تعرض مقاييس مختلفة لأعضاء الفريق في مناطق مختلفة (مثل: APAC مقابل US).
<Note>
**قريبًا**: سنضيف إمكانية تعيين منطقة زمنية محددة للوحة المعلومات، بحيث يرى جميع المستخدمين بيانات متسقة بغض النظر عن مواقعهم.
**قريبًا**: سنضيف إمكانية تعيين منطقة زمنية محددة للوحة المعلومات، بحيث يرى جميع المستخدمين بيانات متسقة بغض النظر عن مواقعهم.
</Note>
<Note>
**قريبًا**: ستتيح عوامل التصفية على مستوى لوحة المعلومات تطبيق عوامل التصفية عبر جميع عناصر واجهة المستخدم دفعة واحدة، مما يجعل استكشاف بياناتك أسرع.
**قريبًا**: ستتيح عوامل التصفية على مستوى لوحة المعلومات تطبيق عوامل التصفية عبر جميع عناصر واجهة المستخدم دفعة واحدة، مما يجعل استكشاف بياناتك أسرع.
</Note>
@@ -14,7 +14,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
اعرض البيانات كأعمدة أفقية أو عمودية.
<Frame>
<img src="/images/user-guide/dashboard/dashboard-widget-bar-chart.png" alt="مخطط أعمدة" />
<img src="/images/user-guide/dashboard/dashboard-widget-bar-chart.png" alt="مخطط أعمدة" />
</Frame>
**الأفضل لـ:**
@@ -30,7 +30,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
* جهات الاتصال المضافة شهريًا
<Note>
**حدود العرض**: يمكن لمخططات الأعمدة عرض حد أقصى يبلغ 100 عمود، و50 مجموعة لكل عمود عند استخدام "Group By". إذا ظهرت لك رسالة التحذير "بيانات غير معروضة: الحد الأقصى X عمودًا لكل مخطط"، فأضِف عوامل تصفية لتضييق نطاق بياناتك أو غيّر أسلوب التجميع (على سبيل المثال، اجعل التجميع حسب الأسبوع بدلًا من الأيام).
**حدود العرض**: يمكن لمخططات الأعمدة عرض حد أقصى يبلغ 100 عمود، و50 مجموعة لكل عمود عند استخدام "Group By". إذا ظهرت لك رسالة التحذير "بيانات غير معروضة: الحد الأقصى X عمودًا لكل مخطط"، فأضِف عوامل تصفية لتضييق نطاق بياناتك أو غيّر أسلوب التجميع (على سبيل المثال، اجعل التجميع حسب الأسبوع بدلًا من الأيام).
</Note>
### <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><ChartIcon icon="chart-pie" />مخططات دائرية</span>
@@ -38,7 +38,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
اعرض نسب الأجزاء من الكل.
<Frame>
<img src="/images/user-guide/dashboard/dashboard-widget-pie-chart.png" alt="مخطط دائري" />
<img src="/images/user-guide/dashboard/dashboard-widget-pie-chart.png" alt="مخطط دائري" />
</Frame>
**الأفضل لـ:**
@@ -58,7 +58,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
اعرض الاتجاهات مع مرور الوقت.
<Frame>
<img src="/images/user-guide/dashboard/dashboard-widget-line-chart.png" alt="مخطط خطي" />
<img src="/images/user-guide/dashboard/dashboard-widget-line-chart.png" alt="مخطط خطي" />
</Frame>
**الأفضل لـ:**
@@ -78,7 +78,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
اعرض القيم الأساسية المفردة بشكل بارز.
<Frame>
<img src="/images/user-guide/dashboard/dashboard-widget-aggregate-chart.png" alt="مخطط تجميعي" />
<img src="/images/user-guide/dashboard/dashboard-widget-aggregate-chart.png" alt="مخطط تجميعي" />
</Frame>
**الأفضل لـ:**
@@ -103,7 +103,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
ضمّن الأدوات والمحتوى الخارجي مباشرةً في لوحة المعلومات الخاصة بك.
<Frame>
<img src="/images/user-guide/dashboard/dashboard-widget-iframe.png" alt="iFrame" />
<img src="/images/user-guide/dashboard/dashboard-widget-iframe.png" alt="iFrame" />
</Frame>
**الأفضل لـ:**
@@ -123,7 +123,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
أضف نصًا منسقًا ومحتوى مباشرةً إلى لوحة المعلومات الخاصة بك.
<Frame>
<img src="/images/user-guide/dashboard/dashboard-widget-rich-text.png" alt="نص منسق" />
<img src="/images/user-guide/dashboard/dashboard-widget-rich-text.png" alt="نص منسق" />
</Frame>
**الأفضل لـ:**
@@ -139,7 +139,7 @@ import { ChartIcon } from '/snippets/chart-icon.mdx';
* تحرير بأسلوب Markdown
<Note>
**قريبًا**: مخططات القياس والجداول غير متاحة بعد ولكنها على خريطة الطريق لدينا.
**قريبًا**: مخططات القياس والجداول غير متاحة بعد ولكنها على خريطة الطريق لدينا.
</Note>
### تكرار الأدوات
@@ -47,7 +47,6 @@ description: أسئلة شائعة حول لوحات القيادة في Twenty.
<Accordion title="ما مدى تكرار تحديث بيانات لوحة القيادة؟">
تتحدّث عناصر واجهة المستخدم تلقائيًا مع تغيّر بيانات نظام إدارة علاقات العملاء (CRM):
* تحديثات فورية لمعظم المقاييس
* استخدم زر التحديث لإجراء تحديث يدوي عند الحاجة
* تُحفَظ البيانات التاريخية لتحليل الاتجاهات
@@ -9,7 +9,7 @@ image: /images/user-guide/reporting/pie-chart.png
</Frame>
<Note>
لوحات المعلومات حاليًا في الإصدار التجريبي. قم بتفعيلها ضمن **الإعدادات → التحديثات → الوصول المبكر**.
لوحات المعلومات حاليًا في الإصدار التجريبي. قم بتفعيلها ضمن **الإعدادات → التحديثات → الوصول المبكر**.
</Note>
## فهم لوحات المعلومات
@@ -45,10 +45,9 @@ image: /images/user-guide/reporting/pie-chart.png
* نص منسق
<Note>
**القيود الحالية**:
* تصدير لوحات المعلومات ومشاركتها مع المستخدمين الخارجيين (غير مستخدمي Twenty) غير متاحين في الوقت الحالي.
* مخططات القياس والجداول غير متاحة بعد.
**القيود الحالية**:
* تصدير لوحات المعلومات ومشاركتها مع المستخدمين الخارجيين (غير مستخدمي Twenty) غير متاحين في الوقت الحالي.
* مخططات القياس والجداول غير متاحة بعد.
</Note>
## البدء
@@ -141,9 +141,9 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>لن يتم إنشاء خيارات التحديد الجديدة تلقائيًا بواسطة الاستيراد. يجب إضافتها ضمن **الإعدادات → نموذج البيانات** قبل الاستيراد.</Note>
<Warning>
**الاستيراد يستبدل، لا يضيف.**
**الاستيراد يستبدل، لا يضيف.**
إذا كان السجل يحتوي بالفعل على `VALUE2` و`VALUE3` محدّدين، ثم استوردت `[\"VALUE1\"]`، فسيحتوي السجل على `VALUE1` فقط بعد الاستيراد. يتم استبدال التحديدات السابقة، وليس دمجها.
إذا كان السجل يحتوي بالفعل على `VALUE2` و`VALUE3` محدّدين، ثم استوردت `[\"VALUE1\"]`، فسيحتوي السجل على `VALUE1` فقط بعد الاستيراد. يتم استبدال التحديدات السابقة، وليس دمجها.
</Warning>
### حقول الأرقام
@@ -44,12 +44,12 @@ description: استيراد العلاقات بين السجلات عبر CSV.
<img src="/images/user-guide/fields/one-to-many-morph.png" style={{width:'100%'}} />
<Warning>
**قيد في الاستيراد/التصدير**: العلاقات التي تشير إلى أنواع كائنات متعددة (مثل الملاحظات → الأشخاص/الشركات/الفرص) **غير مدعومة بعد** في استيراد أو تصدير CSV.
**قيد في الاستيراد/التصدير**: العلاقات التي تشير إلى أنواع كائنات متعددة (مثل الملاحظات → الأشخاص/الشركات/الفرص) **غير مدعومة بعد** في استيراد أو تصدير CSV.
* **الاستيراد**: يمكن استيراد علاقات واحد إلى متعدد فقط التي تشير إلى نوع كائن واحد على كل جانب
* **التصدير**: تُترك الأعمدة الخاصة بالعلاقات التي تشير إلى أنواع كائنات متعددة فارغة حاليًا
* **الاستيراد**: يمكن استيراد علاقات واحد إلى متعدد فقط التي تشير إلى نوع كائن واحد على كل جانب
* **التصدير**: تُترك الأعمدة الخاصة بالعلاقات التي تشير إلى أنواع كائنات متعددة فارغة حاليًا
هذا على خارطة الطريق لدينا.
هذا على خارطة الطريق لدينا.
</Warning>
### ما ليس مدعومًا اليوم
@@ -137,12 +137,12 @@ Jane,Doe,jane@widgets.co,https://widgets.co
## قاعدة ترتيب الاستيراد
<Warning>
**احرص دائمًا على استيراد جانب "الواحد" أولًا!**
**احرص دائمًا على استيراد جانب "الواحد" أولًا!**
1. **الشركات** أولًا (من دون تبعيات)
2. **الأشخاص** ثانيًا (مرتبطون بالشركات)
3. **الفرص** ثالثًا (مرتبطة بالشركات/الأشخاص)
4. **الكائنات المخصّصة** وفقًا لتبعياتها
1. **الشركات** أولًا (من دون تبعيات)
2. **الأشخاص** ثانيًا (مرتبطون بالشركات)
3. **الفرص** ثالثًا (مرتبطة بالشركات/الأشخاص)
4. **الكائنات المخصّصة** وفقًا لتبعياتها
يجب أن يكون السجل الأصل موجودًا قبل أن تتمكّن من الإشارة إليه.
يجب أن يكون السجل الأصل موجودًا قبل أن تتمكّن من الإشارة إليه.
</Warning>
@@ -50,9 +50,9 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
### السجلات المحذوفة حذفًا غير نهائي
<Warning>
**السجلات المحذوفة تُحتسب ضمن التفرّد.**
**السجلات المحذوفة تُحتسب ضمن التفرّد.**
السجلات المحذوفة حذفًا غير نهائي (مرئية ضمن Command Menu → See deleted records) مُدرجة ضمن فحوصات التفرّد. إذا قمت باستيراد سجل بالقيمة الفريدة نفسها لسجل محذوف، فسيتم **استعادة** السجل المحذوف مع البيانات الجديدة.
السجلات المحذوفة حذفًا غير نهائي (مرئية ضمن Command Menu → See deleted records) مُدرجة ضمن فحوصات التفرّد. إذا قمت باستيراد سجل بالقيمة الفريدة نفسها لسجل محذوف، فسيتم **استعادة** السجل المحذوف مع البيانات الجديدة.
</Warning>
## اكتشاف التكرارات أثناء الاستيراد
@@ -71,12 +71,12 @@ description: دليل شامل لاستكشاف أخطاء استيراد CSV و
3. ستقوم Twenty بتحديث السجل الموجود بدلًا من إنشاء نسخة مكرّرة
<Note>
**يمكنك تحديث الحقول الفريدة أيضًا.**
**يمكنك تحديث الحقول الفريدة أيضًا.**
* إذا أبقيت على `id` لكن غيّرت `email` → فسيتم تحديث البريد الإلكتروني
* إذا أبقيت على `email` لكن غيّرت `id` → فسيتم تحديث المعرّف
* إذا أبقيت على `id` لكن غيّرت `email` → فسيتم تحديث البريد الإلكتروني
* إذا أبقيت على `email` لكن غيّرت `id` → فسيتم تحديث المعرّف
طالما أن أحد المعرّفات الفريدة يطابق، تقوم Twenty بتحديث السجل.
طالما أن أحد المعرّفات الفريدة يطابق، تقوم Twenty بتحديث السجل.
</Note>
### كيفية منع هذا الخطأ
@@ -88,9 +88,9 @@ description: دليل شامل لاستكشاف أخطاء استيراد CSV و
3. تحقّق مما إذا كانت السجلات موجودة مسبقًا في Twenty
<Warning>
**السجلات المحذوفة حذفًا ناعمًا تُحتسب ضمن التفرد.**
**السجلات المحذوفة حذفًا ناعمًا تُحتسب ضمن التفرد.**
تحقّق من قائمة الأوامر → اعرض السجلات المحذوفة. السجلات هناك ما تزال تفرض شرط التفرد. احذفها نهائيًا أو استعدها وقم بتحديثها.
تحقّق من قائمة الأوامر → اعرض السجلات المحذوفة. السجلات هناك ما تزال تفرض شرط التفرد. احذفها نهائيًا أو استعدها وقم بتحديثها.
</Warning>
لمزيد من التفاصيل: [قيود التفرد](/l/ar/user-guide/data-migration/capabilities/uniqueness-constraints)
@@ -296,9 +296,9 @@ Can't connect to company. No unique record found with condition: id = 7776ee49-f
* تحقّق من الأخطاء الإملائية وحساسية حالة الأحرف
<Warning>
**سيفشل الاستيراد إذا تمت الإشارة إلى سجل غير موجود.**
**سيفشل الاستيراد إذا تمت الإشارة إلى سجل غير موجود.**
استورد دائمًا كائنات الأصل قبل كائنات التابع.
استورد دائمًا كائنات الأصل قبل كائنات التابع.
</Warning>
لمزيد من التفاصيل: [استيراد العلاقات](/l/ar/user-guide/data-migration/capabilities/import-relations)
@@ -372,9 +372,9 @@ Too many records. Up to 10000 allowed
3. استخدم البريد الإلكتروني نفسه الذي استخدموه للانضمام إلى Twenty
<Warning>
**يجب على المستخدمين قبول الدعوات قبل الاستيراد.**
**يجب على المستخدمين قبول الدعوات قبل الاستيراد.**
إذا لم يقبل المستخدم دعوته، فستكون علاقات المستخدم فارغة في السجلات التي تشير إليه.
إذا لم يقبل المستخدم دعوته، فستكون علاقات المستخدم فارغة في السجلات التي تشير إليه.
</Warning>
---
@@ -12,23 +12,23 @@ description: دليل كامل خطوة بخطوة لاستيراد الشركا
### قائمة المتطلبات المسبقة
<Check>
يجب أن يكون ملفك بتنسيق CSV أو XLSX أو XLS
يجب أن يكون ملفك بتنسيق CSV أو XLSX أو XLS
</Check>
<Check>
يحتوي الملف على أقل من 10,000 سجل
يحتوي الملف على أقل من 10,000 سجل
</Check>
<Check>
لا توجد نطاقات مكررة في ملفك
لا توجد نطاقات مكررة في ملفك
</Check>
<Check>
جميع الحقول المخصصة موجودة في **Settings → Data Model**
جميع الحقول المخصصة موجودة في **Settings → Data Model**
</Check>
<Note>
هل تحتاج إلى استيراد أكثر من 10,000 شركة؟ قسّم إلى ملفات متعددة أو استخدم [استيراد عبر API](/l/ar/user-guide/data-migration/how-tos/import-data-via-api).
هل تحتاج إلى استيراد أكثر من 10,000 شركة؟ قسّم إلى ملفات متعددة أو استخدم [استيراد عبر API](/l/ar/user-guide/data-migration/how-tos/import-data-via-api).
</Note>
## الخطوة 1: جهّز بيانات شركتك
@@ -46,9 +46,9 @@ description: دليل كامل خطوة بخطوة لاستيراد الشركا
### تنسيق النطاق
<Warning>
**استخدم التنسيق `https://domain.com` للنطاقات.**
**استخدم التنسيق `https://domain.com` للنطاقات.**
هذا يطابق التنسيق المستخدم عند إنشاء الشركات تلقائيًا من مزامنة البريد الإلكتروني/التقويم، مما يمنع التكرارات لاحقًا.
هذا يطابق التنسيق المستخدم عند إنشاء الشركات تلقائيًا من مزامنة البريد الإلكتروني/التقويم، مما يمنع التكرارات لاحقًا.
</Warning>
**أعمدة النطاق:**
@@ -74,7 +74,7 @@ Widget Co,https://widgets.co,widgets.co,New York,USA,50
```
<Note>
**نصيحة احترافية:** انقر **Download sample file** أثناء الاستيراد للاطلاع على أسماء الأعمدة الدقيقة التي تتوقعها Twenty.
**نصيحة احترافية:** انقر **Download sample file** أثناء الاستيراد للاطلاع على أسماء الأعمدة الدقيقة التي تتوقعها Twenty.
</Note>
## الخطوة 2: الوصول إلى ميزة الاستيراد
@@ -123,7 +123,7 @@ Widget Co,https://widgets.co,widgets.co,New York,USA,50
3. أو أنشئ خيارات جديدة عند الحاجة
<Note>
تستخدم خيارات الاختيار **أسماء API**، وليس تسميات العرض. تحقّق من **Settings → Data Model** → فعِّل **Advanced mode** لمشاهدة أسماء API.
تستخدم خيارات الاختيار **أسماء API**، وليس تسميات العرض. تحقّق من **Settings → Data Model** → فعِّل **Advanced mode** لمشاهدة أسماء API.
</Note>
## الخطوة 6: مراجعة الأخطاء وإصلاحها
@@ -12,29 +12,29 @@ description: دليل كامل خطوة بخطوة لاستيراد الأشخا
### قائمة المتطلبات المسبقة
<Check>
يجب أن يكون ملفك بتنسيق CSV أو XLSX أو XLS
يجب أن يكون ملفك بتنسيق CSV أو XLSX أو XLS
</Check>
<Check>
يحتوي الملف على أقل من 10,000 سجل
يحتوي الملف على أقل من 10,000 سجل
</Check>
<Check>
لا توجد عناوين بريد إلكتروني مكررة في ملفك
لا توجد عناوين بريد إلكتروني مكررة في ملفك
</Check>
<Check>
**استيراد الشركات أولًا** (إذا كنت تربط الأشخاص بالشركات)
**استيراد الشركات أولًا** (إذا كنت تربط الأشخاص بالشركات)
</Check>
<Check>
جميع الحقول المخصصة موجودة في **Settings → Data Model**
جميع الحقول المخصصة موجودة في **Settings → Data Model**
</Check>
<Warning>
**استيراد الشركات قبل الأشخاص**
**استيراد الشركات قبل الأشخاص**
إذا كنت تريد ربط الأشخاص بالشركات، فاستورد الشركات أولًا. يجب أن تكون الشركة موجودة قبل أن تتمكن من الإشارة إليها.
إذا كنت تريد ربط الأشخاص بالشركات، فاستورد الشركات أولًا. يجب أن تكون الشركة موجودة قبل أن تتمكن من الإشارة إليها.
</Warning>
## الخطوة 1: جهّز بيانات جهات الاتصال الخاصة بك
@@ -81,7 +81,7 @@ description: دليل كامل خطوة بخطوة لاستيراد الأشخا
| `companyId` | UUID | `c776ee49-f608-4a77-8cc8-6fe96ae1e43f` |
<Note>
**استخدم تنسيق عنوان URL للنطاق** (`https://acme.com`)، وليس التسمية. هذا يطابق كيفية تخزين الشركات في Twenty.
**استخدم تنسيق عنوان URL للنطاق** (`https://acme.com`)، وليس التسمية. هذا يطابق كيفية تخزين الشركات في Twenty.
</Note>
### بنية CSV نموذجية
@@ -93,7 +93,7 @@ Jane,Doe,jane@widgets.co,CTO,https://widgets.co,2125551234,US
```
<Note>
**نصيحة احترافية:** انقر **Download sample file** أثناء الاستيراد أو صدّر عددًا قليلًا من الأشخاص الموجودين للاطلاع على أسماء الأعمدة الدقيقة التي تتوقعها Twenty.
**نصيحة احترافية:** انقر **Download sample file** أثناء الاستيراد أو صدّر عددًا قليلًا من الأشخاص الموجودين للاطلاع على أسماء الأعمدة الدقيقة التي تتوقعها Twenty.
</Note>
## الخطوة 2: الوصول إلى ميزة الاستيراد
@@ -145,9 +145,9 @@ Jane,Doe,jane@widgets.co,CTO,https://widgets.co,2125551234,US
3. سيقوم Twenty بربط كل شخص بالشركة المطابقة
<Warning>
**طابق معرّفًا فريدًا واحدًا فقط للعلاقات.**
**طابق معرّفًا فريدًا واحدًا فقط للعلاقات.**
لا تقم بتعيين كلٍّ من `companyId` و`companyDomain` معًا. اختر واحدًا — ويفضَّل استخدام النطاق لأنه مقروء للبشر.
لا تقم بتعيين كلٍّ من `companyId` و`companyDomain` معًا. اختر واحدًا — ويفضَّل استخدام النطاق لأنه مقروء للبشر.
</Warning>
## الخطوة 5: تعيين قيم حقل الاختيار
@@ -159,7 +159,7 @@ Jane,Doe,jane@widgets.co,CTO,https://widgets.co,2125551234,US
3. أو أنشئ خيارات جديدة عند الحاجة
<Note>
تستخدم خيارات الاختيار **أسماء API**، وليس تسميات العرض. تحقّق من **الإعدادات → نموذج البيانات** → فعِّل **الوضع المتقدم** لمشاهدة أسماء API.
تستخدم خيارات الاختيار **أسماء API**، وليس تسميات العرض. تحقّق من **الإعدادات → نموذج البيانات** → فعِّل **الوضع المتقدم** لمشاهدة أسماء API.
</Note>
## الخطوة 6: مراجعة الأخطاء وإصلاحها
@@ -32,9 +32,9 @@ description: متى وكيف تستخدم واجهات API الخاصة بـ Twe
| **الحد الأقصى للإنتاجية** | ~6,000 سجل/دقيقة |
<Warning>
**خطّط لعملية الاستيراد وفق هذه الحدود.**
**خطّط لعملية الاستيراد وفق هذه الحدود.**
بالنسبة إلى 100,000 سجل عند أقصى إنتاجية، توقّع نحو 17 دقيقة من وقت الاستيراد. أضِف وقتًا احتياطيًا لمعالجة الأخطاء وإعادة المحاولة.
بالنسبة إلى 100,000 سجل عند أقصى إنتاجية، توقّع نحو 17 دقيقة من وقت الاستيراد. أضِف وقتًا احتياطيًا لمعالجة الأخطاء وإعادة المحاولة.
</Warning>
## البدء
@@ -48,9 +48,9 @@ description: متى وكيف تستخدم واجهات API الخاصة بـ Twe
5. احفظه في مكان آمن
<Warning>
**أبقِ مفتاح API سريًا.**
**أبقِ مفتاح API سريًا.**
يمكن لأي شخص لديه مفتاح API الخاص بك الوصول إلى بيانات مساحة العمل الخاصة بك وتعديلها. لا تقم أبدًا بإيداعه في مستودعات الكود أو مشاركته علنًا.
يمكن لأي شخص لديه مفتاح API الخاص بك الوصول إلى بيانات مساحة العمل الخاصة بك وتعديلها. لا تقم أبدًا بإيداعه في مستودعات الكود أو مشاركته علنًا.
</Warning>
### الخطوة 2: اختر واجهة API الخاصة بك
@@ -51,9 +51,9 @@ description: دليل كامل خطوة بخطوة لربط السجلات أث
4. **المهام/الملاحظات** (مرتبطة بأي مما سبق)
<Warning>
**إذا كان سجل الأصل غير موجود، فسيفشل الاستيراد.**
**إذا كان سجل الأصل غير موجود، فسيفشل الاستيراد.**
تحقّق دائمًا من استيراد الشركات قبل استيراد الأشخاص الذين لديهم مراجع للشركات.
تحقّق دائمًا من استيراد الشركات قبل استيراد الأشخاص الذين لديهم مراجع للشركات.
</Warning>
## الخطوة 3: تدوين المعرّف الفريد للأصل
@@ -117,9 +117,9 @@ Bob,Johnson,bob@techstart.io,Developer,https://techstart.io
| المعرف | UUID | `c776ee49-f608-4a77-8cc8-6fe96ae1e43f` |
<Warning>
**تنسيق النطاق مهم!**
**تنسيق النطاق مهم!**
استخدم `https://domain.com` (وليس فقط `domain.com`). هذا يتوافق مع طريقة تخزين Twenty لنطاقات الشركات ويمنع أخطاء المطابقة.
استخدم `https://domain.com` (وليس فقط `domain.com`). هذا يتوافق مع طريقة تخزين Twenty لنطاقات الشركات ويمنع أخطاء المطابقة.
</Warning>
### قواعد مهمة
@@ -178,7 +178,6 @@ Review proposal,jane@yourcompany.com
<AccordionGroup>
<Accordion title="ماذا لو لم يكن لدى سجل الأصل نطاق أو بريد إلكتروني؟">
لديك خياران:
1. استخدم `id` الخاص بـ Twenty (صدّر سجلات الأصل للحصول على معرّفاتها)
2. أنشئ حقلًا فريدًا مخصصًا في نموذج البيانات لتخزين معرّف خارجي من نظامك السابق
</Accordion>
@@ -197,7 +196,6 @@ Review proposal,jane@yourcompany.com
<Accordion title="ماذا يحدث إذا كان سجل الأصل غير موجود؟">
سيعرض الاستيراد خطأً لذلك الصف. يمكنك إما:
* استورد سجل الأصل أولًا، ثم أعد الاستيراد
* أصلِح قيمة المرجع
* أزل الصف من الاستيراد
@@ -205,7 +203,6 @@ Review proposal,jane@yourcompany.com
<Accordion title="لماذا لا يتم ربط العلاقة بشكل صحيح؟">
أسباب شائعة:
* تنسيق غير صحيح (استخدم `https://domain.com` للنطاقات)
* عدم تطابق حالة الأحرف (تحقّق من التهجئة الدقيقة)
* الأصل غير موجود (استورد الأصول أولًا)
@@ -214,9 +211,9 @@ Review proposal,jane@yourcompany.com
</AccordionGroup>
<Warning>
**تذكير: السجلات المحذوفة حذفًا ناعمًا تُحتسب ضمن التفرد.**
**تذكير: السجلات المحذوفة حذفًا ناعمًا تُحتسب ضمن التفرد.**
إذا كنت تتلقى أخطاء "not found" ولكن يبدو أن السجل موجود، فتحقّق من قائمة الأوامر → عرض السجلات المحذوفة. قد يكون السجل الأصل قد حُذِف حذفًا منطقيًا.
إذا كنت تتلقى أخطاء "not found" ولكن يبدو أن السجل موجود، فتحقّق من قائمة الأوامر → عرض السجلات المحذوفة. قد يكون السجل الأصل قد حُذِف حذفًا منطقيًا.
</Warning>
## استكشاف الأخطاء وإصلاحها
@@ -59,17 +59,17 @@ description: دليل خطوة بخطوة لترحيل بياناتك من أي
4. اضبط إعدادات الحقول (فريد، مطلوب، خيارات الاختيار، إلخ.)
<Warning>
**يجب أن تكون الحقول موجودة قبل الاستيراد.**
**يجب أن تكون الحقول موجودة قبل الاستيراد.**
يُنشئ استيراد CSV سجلات، وليس حقولًا. أنشِئ جميع الحقول المخصّصة ضمن الإعدادات → نموذج البيانات قبل الاستيراد.
يُنشئ استيراد CSV سجلات، وليس حقولًا. أنشِئ جميع الحقول المخصّصة ضمن الإعدادات → نموذج البيانات قبل الاستيراد.
</Warning>
### ادعُ فريقك
<Warning>
**ادعُ المستخدمين قبل استيراد البيانات.**
**ادعُ المستخدمين قبل استيراد البيانات.**
إذا كانت بياناتك تتضمن مراجع للمستخدمين (مالك الحساب، المكلَّف، إلخ.)، فيجب أن يكون هؤلاء المستخدمون موجودين في Twenty قبل الاستيراد. وإلا فلن يمكن تعيين تلك العلاقات.
إذا كانت بياناتك تتضمن مراجع للمستخدمين (مالك الحساب، المكلَّف، إلخ.)، فيجب أن يكون هؤلاء المستخدمون موجودين في Twenty قبل الاستيراد. وإلا فلن يمكن تعيين تلك العلاقات.
</Warning>
1. انتقل إلى **الإعدادات → الأعضاء**
@@ -116,9 +116,9 @@ description: دليل خطوة بخطوة لترحيل بياناتك من أي
| **حقول الاختيار** | استخدم أسماء API، وليس تسميات العرض |
<Warning>
**تنسيق النطاق مهم.**
**تنسيق النطاق مهم.**
استخدم `https://domain.com` (وليس `domain.com` أو `www.domain.com`). هذا يتوافق مع تنسيق Twenty ويمنع التكرارات عند توصيل مزامنة البريد الإلكتروني/التقويم.
استخدم `https://domain.com` (وليس `domain.com` أو `www.domain.com`). هذا يتوافق مع تنسيق Twenty ويمنع التكرارات عند توصيل مزامنة البريد الإلكتروني/التقويم.
</Warning>
اطّلع على [كيفية إعداد ملفات CSV](/l/ar/user-guide/data-migration/how-tos/prepare-your-csv-files) لمعرفة متطلبات التنسيق الكاملة لجميع أنواع الحقول.
@@ -149,17 +149,16 @@ Jane,Doe,jane@widgets.co,https://widgets.co
## الخطوة 6: الاستيراد إلى Twenty
<Warning>
**ترتيب الاستيراد مهم!**
**ترتيب الاستيراد مهم!**
استورد دائمًا بهذا الترتيب:
استورد دائمًا بهذا الترتيب:
1. **الشركات** أولًا (من دون تبعيات)
2. **الأشخاص** ثانيًا (مرتبطون بالشركات)
3. **الفرص** ثالثًا (مرتبطة بالشركات/الأشخاص)
4. **الملاحظات والمهام** (مرتبطة بالسجلات)
5. **الكائنات المخصّصة** وفقًا لتبعياتها
1. **الشركات** أولًا (من دون تبعيات)
2. **الأشخاص** ثانيًا (مرتبطون بالشركات)
3. **الفرص** ثالثًا (مرتبطة بالشركات/الأشخاص)
4. **الملاحظات والمهام** (مرتبطة بالسجلات)
5. **الكائنات المخصّصة** وفقًا لتبعياتها
يجب أن يكون السجل الأصل موجودًا قبل أن تتمكّن من الإشارة إليه.
يجب أن يكون السجل الأصل موجودًا قبل أن تتمكّن من الإشارة إليه.
</Warning>
### استيراد كل كائن
@@ -29,9 +29,9 @@ description: دليل خطوة بخطوة لترحيل مثيل Twenty ذاتي
## الخطوة 3: دعوة جميع المستخدمين
<Warning>
**هام: قم بدعوة المستخدمين قبل استيراد البيانات.**
**هام: قم بدعوة المستخدمين قبل استيراد البيانات.**
يجب على المستخدمين قبول دعواتهم قبل أن تستورد أي سجلات تُشير إليهم (مثل حقول مالك الحساب). إذا لم يكن المستخدمون موجودين بعد، فلن يمكن تعيين تلك العلاقات.
يجب على المستخدمين قبول دعواتهم قبل أن تستورد أي سجلات تُشير إليهم (مثل حقول مالك الحساب). إذا لم يكن المستخدمون موجودين بعد، فلن يمكن تعيين تلك العلاقات.
</Warning>
1. انتقل إلى **الإعدادات → الأعضاء** في مثيلك السحابي
@@ -210,15 +210,14 @@ Jane,Doe,jane@widgets.co,https://widgets.co
* بالنسبة لأعضاء مساحة العمل، استخدم **البريد الإلكتروني** الخاص بهم (وليس الاسم)
<Warning>
**ترتيب الاستيراد مهم!**
**ترتيب الاستيراد مهم!**
استورد طرف "الواحد" قبل طرف "الكثير":
استورد طرف "الواحد" قبل طرف "الكثير":
1. **الشركات** أولاً
2. **الأشخاص** ثانيًا (مع مرجع الشركة)
3. **الفرص** ثالثًا
1. **الشركات** أولاً
2. **الأشخاص** ثانيًا (مع مرجع الشركة)
3. **الفرص** ثالثًا
يجب أن يوجد السجل الأب قبل أن تتمكن من الإشارة إليه.
يجب أن يوجد السجل الأب قبل أن تتمكن من الإشارة إليه.
</Warning>
اطلع على [كيفية استيراد العلاقات](/l/ar/user-guide/data-migration/how-tos/import-relations-between-objects-via-csv) للحصول على تعليمات مفصلة.
@@ -25,11 +25,11 @@ description: دليل كامل خطوة بخطوة لتحديث السجلات
| مفقود من ملفك | **إنشاء** سجل جديد (بمُعرّف يتم إنشاؤه تلقائيًا) |
<Warning>
**يتم استبدال حقول الاختيار المتعدد، ولا يتم دمجها.**
**يتم استبدال حقول الاختيار المتعدد، ولا يتم دمجها.**
إذا كان السجل محدّدًا فيه `Option A` و`Option B`، وقمتَ باستيراد `[\"Option C\"]`، فسيحتوي السجل على `Option C` فقط بعد الاستيراد. تستبدل عملية الاستيراد جميع الاختيارات السابقة — ولا تضيف إليها.
إذا كان السجل محدّدًا فيه `Option A` و`Option B`، وقمتَ باستيراد `[\"Option C\"]`، فسيحتوي السجل على `Option C` فقط بعد الاستيراد. تستبدل عملية الاستيراد جميع الاختيارات السابقة — ولا تضيف إليها.
للاحتفاظ بالقيم الحالية، ضمّنها جميعًا في عملية الاستيراد: `[\"Option A\",\"Option B\",\"Option C\"]`
للاحتفاظ بالقيم الحالية، ضمّنها جميعًا في عملية الاستيراد: `[\"Option A\",\"Option B\",\"Option C\"]`
</Warning>
## الخطوة 1: تصدير بياناتك الحالية
@@ -80,9 +80,9 @@ id,email,firstName,lastName,jobTitle
```
<Warning>
**لا تغيّر قيم المعرّفات الفريدة.**
**لا تغيّر قيم المعرّفات الفريدة.**
إذا غيّرتَ `john@acme.com` إلى `john.smith@acme.com`، فسيقوم Twenty بإنشاء سجل جديد بدلاً من تحديث السجل الموجود.
إذا غيّرتَ `john@acme.com` إلى `john.smith@acme.com`، فسيقوم Twenty بإنشاء سجل جديد بدلاً من تحديث السجل الموجود.
</Warning>
## الخطوة 3: استيراد الملف المُحدَّث
@@ -44,16 +44,15 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
<Note>**تُحتسب السجلات المحذوفة ضمن التفرّد.** السجلات المحذوفة حذفاً ناعماً (المرئية ضمن قائمة الأوامر → عرض السجلات المحذوفة) تُدرج ضمن عمليات التحقق من التفرّد. إذا قمتَ باستيراد سجل بالقيمة الفريدة نفسها لسجل محذوف، فسيتم استعادة السجل المحذوف.</Note>
<Warning>
**ترتيب الاستيراد مهم!**
**ترتيب الاستيراد مهم!**
عند استيراد أجسام مترابطة، ارفع الملفات بهذا الترتيب:
عند استيراد أجسام مترابطة، ارفع الملفات بهذا الترتيب:
1. **الشركات** أولاً (جانب "الواحد" من العلاقات)
2. **الأفراد** ثانياً (مرتبطون بالشركات عبر companyId)
3. **الفرص** ثالثاً (مرتبطة بالشركات/الأفراد)
4. **الأجسام المخصّصة** ذات العلاقات أخيراً
1. **الشركات** أولاً (جانب "الواحد" من العلاقات)
2. **الأفراد** ثانياً (مرتبطون بالشركات عبر companyId)
3. **الفرص** ثالثاً (مرتبطة بالشركات/الأفراد)
4. **الأجسام المخصّصة** ذات العلاقات أخيراً
لماذا؟ يجب أن يكون جانب "الواحد" من علاقة واحد إلى متعدد موجودًا قبل أن تتمكن من الإشارة إليه. على سبيل المثال، يجب أن يكون سجل الشركة موجودًا قبل أن تستورد شخصًا يحمل معرّف تلك الشركة.
لماذا؟ يجب أن يكون جانب "الواحد" من علاقة واحد إلى متعدد موجودًا قبل أن تتمكن من الإشارة إليه. على سبيل المثال، يجب أن يكون سجل الشركة موجودًا قبل أن تستورد شخصًا يحمل معرّف تلك الشركة.
</Warning>
يرجى الرجوع إلى [هذه المقالة](/l/ar/user-guide/data-migration/how-tos/import-relations-between-objects-via-csv) للاطلاع على دليل خطوة بخطوة حول كيفية المتابعة.
@@ -30,7 +30,7 @@ description: اربط السجلات عبر كائنات مختلفة باستخ
<img src="/images/user-guide/fields/one-to-many-morph.png" style={{width:'100%'}} />
<Warning>
**قيد الاستيراد/التصدير**: العلاقات التي تشير إلى أنواع كائنات متعددة غير مدعومة بعد لاستيراد/تصدير CSV. هذا على خارطة الطريق لدينا.
**قيد الاستيراد/التصدير**: العلاقات التي تشير إلى أنواع كائنات متعددة غير مدعومة بعد لاستيراد/تصدير CSV. هذا على خارطة الطريق لدينا.
</Warning>
### متعدد-إلى-متعدد
@@ -44,7 +44,7 @@ description: اربط السجلات عبر كائنات مختلفة باستخ
<img src="/images/user-guide/fields/junction-relation-diagram.png" style={{width:'100%'}} />
<Warning>
**ميزة المختبر**: يجب تمكين علاقات الربط في **الإعدادات → التحديثات → المختبر** قبل الاستخدام.
**ميزة المختبر**: يجب تمكين علاقات الربط في **الإعدادات → التحديثات → المختبر** قبل الاستخدام.
</Warning>
راجع [كيفية إنشاء علاقات متعدد-إلى-متعدد](/l/ar/user-guide/data-model/how-tos/create-many-to-many-relations) للحصول على دليل كامل خطوة بخطوة.
@@ -35,7 +35,7 @@ description: دليل خطوة بخطوة لإضافة حقول مخصصة إل
3. اسحب لإعادة ترتيب الخيارات
<Note>
**استخدم أسماء واجهة برمجة التطبيقات (API) لعمليات الاستيراد.** فعّل **الوضع المتقدم** في الإعدادات لعرض أسماء واجهة برمجة التطبيقات. راجع [تعيين الحقول](/l/ar/user-guide/data-migration/capabilities/field-mapping).
**استخدم أسماء واجهة برمجة التطبيقات (API) لعمليات الاستيراد.** فعّل **الوضع المتقدم** في الإعدادات لعرض أسماء واجهة برمجة التطبيقات. راجع [تعيين الحقول](/l/ar/user-guide/data-migration/capabilities/field-mapping).
</Note>
### لحقول العملة
@@ -51,7 +51,7 @@ description: دليل خطوة بخطوة لإضافة حقول مخصصة إل
فعّل **فريد** لمنع تكرار القيم عبر السجلات.
<Note>
إذا وُجدت قيم مكررة (بما في ذلك في السجلات المحذوفة)، ستظهر رسالة خطأ. أزل التكرارات أولًا.
إذا وُجدت قيم مكررة (بما في ذلك في السجلات المحذوفة)، ستظهر رسالة خطأ. أزل التكرارات أولًا.
</Note>
### تعيين القيم الافتراضية
@@ -6,7 +6,7 @@ description: دليل خطوة بخطوة لإنشاء كائنات مخصصة
تتيح لك الكائنات المخصصة تخزين معلومات فريدة لعملك لا تغطيها الكائنات القياسية. على سبيل المثال: المشاريع، المنتجات، التذاكر، أو القوائم.
<Note>
**لست متأكدًا مما إذا كنت بحاجة إلى كائن أم حقل؟** اطّلع على [فهم نموذج البيانات لديك](/l/ar/user-guide/data-model/overview) للحصول على إرشادات.
**لست متأكدًا مما إذا كنت بحاجة إلى كائن أم حقل؟** اطّلع على [فهم نموذج البيانات لديك](/l/ar/user-guide/data-model/overview) للحصول على إرشادات.
</Note>
## الخطوات
@@ -6,11 +6,11 @@ description: اربط السجلات حيث يمكن ربط عناصر كثير
تتيح علاقات متعدد-إلى-متعدد ربط سجلات متعددة على كلا الجانبين. على سبيل المثال: يمكن للعديد من الأشخاص العمل على العديد من المشاريع، ويمكن لكل مشروع أن يضم العديد من الأشخاص.
<Warning>
**ميزة المختبر**: علاقات الربط متاحة حاليًا في المختبر. فعِّلها في **الإعدادات → التحديثات → المختبر** قبل اتباع هذا الدليل.
**ميزة المختبر**: علاقات الربط متاحة حاليًا في المختبر. فعِّلها في **الإعدادات → التحديثات → المختبر** قبل اتباع هذا الدليل.
</Warning>
<Note>
تتطلب هذه الميزة أيضًا تفعيل **الوضع المتقدم** (زر التبديل في أسفل يمين صفحة الإعدادات).
تتطلب هذه الميزة أيضًا تفعيل **الوضع المتقدم** (زر التبديل في أسفل يمين صفحة الإعدادات).
</Note>
## متى نستخدم علاقات متعدد-إلى-متعدد
@@ -56,7 +56,7 @@ People ←→ Project Assignments ←→ Projects
4. انقر على **حفظ**
<Tip>
**اتفاقية التسمية**: استخدم اسمًا يصف العلاقة، مثل "تعيين مشروع" أو "عضوية الفريق". هذا يجعل نموذج البيانات أسهل في الفهم.
**اتفاقية التسمية**: استخدم اسمًا يصف العلاقة، مثل "تعيين مشروع" أو "عضوية الفريق". هذا يجعل نموذج البيانات أسهل في الفهم.
</Tip>
## الخطوة 2: إنشاء علاقات من كائن الربط
@@ -6,7 +6,7 @@ description: دليل خطوة بخطوة لربط الكائنات باستخد
تربط حقول العلاقة السجلات من كائنات مختلفة—على سبيل المثال، ربط الأشخاص بالشركات.
<Note>
**لا يمكن تغيير أسماء العلاقات بعد إنشائها** (إذ تؤثر في واجهة برمجة التطبيقات API). خطط لأسماء العلاقات بعناية.
**لا يمكن تغيير أسماء العلاقات بعد إنشائها** (إذ تؤثر في واجهة برمجة التطبيقات API). خطط لأسماء العلاقات بعناية.
</Note>
## قبل أن تبدأ
@@ -51,7 +51,7 @@ description: دليل خطوة بخطوة لربط الكائنات باستخد
تظل الروابط محفوظة لكنها مخفية. أعد التنشيط لاستعادتها.
<Note>
**حذف العلاقة لا يحذف السجلات.** تتم إزالة الرابط بينهما فقط.
**حذف العلاقة لا يحذف السجلات.** تتم إزالة الرابط بينهما فقط.
</Note>
## ذات صلة
@@ -6,148 +6,147 @@ description: الأسئلة الشائعة حول نموذج بيانات Twenty
## إدارة الكائنات
<AccordionGroup>
<Accordion title="هل يمكنني حذف كائن مخصص؟">
نعم، يمكن حذف الكائنات المخصصة. يمكنك أيضًا إلغاء تنشيطها أولًا، مما يخفي الكائن وبياناته من الواجهة مع الاحتفاظ بالبيانات.
</Accordion>
<Accordion title="هل يمكنني حذف كائن مخصص؟">
نعم، يمكن حذف الكائنات المخصصة. يمكنك أيضًا إلغاء تنشيطها أولًا، مما يخفي الكائن وبياناته من الواجهة مع الاحتفاظ بالبيانات.
</Accordion>
<Accordion title="هل يمكنني حذف الكائنات القياسية (الأشخاص، الشركات، إلخ)؟">
لا، لا يمكن حذف الكائنات القياسية. يمكنك فقط إلغاء تنشيطها، مما يخفيها من الواجهة مع الاحتفاظ بالبيانات.
</Accordion>
<Accordion title="هل يمكنني حذف الكائنات القياسية (الأشخاص، الشركات، إلخ)؟">
لا، لا يمكن حذف الكائنات القياسية. يمكنك فقط إلغاء تنشيطها، مما يخفيها من الواجهة مع الاحتفاظ بالبيانات.
</Accordion>
<Accordion title="كم عدد الكائنات المخصصة التي يمكنني إنشاؤها؟">
يمكنك إنشاء ما تشاء من الكائنات والحقول المخصصة — لن يتغير السعر.
</Accordion>
<Accordion title="كم عدد الكائنات المخصصة التي يمكنني إنشاؤها؟">
يمكنك إنشاء ما تشاء من الكائنات والحقول المخصصة — لن يتغير السعر.
</Accordion>
<Accordion title="هل يمكنني إعادة تسمية كائن قياسي؟">
يمكنك تغيير تسمية الكائنات القياسية (People وCompanies وOpportunities)، لكن لا يمكنك تغيير أسماء API الخاصة بها. أسماء API ثابتة لضمان الاتساق عبر جميع مساحات عمل Twenty.
</Accordion>
<Accordion title="هل يمكنني إعادة تسمية كائن قياسي؟">
يمكنك تغيير تسمية الكائنات القياسية (People وCompanies وOpportunities)، لكن لا يمكنك تغيير أسماء API الخاصة بها. أسماء API ثابتة لضمان الاتساق عبر جميع مساحات عمل Twenty.
</Accordion>
<Accordion title="هل يمكنني تغيير أيقونة الكائن؟">
نعم، يمكنك تغيير الأيقونة لكلٍ من الكائنات القياسية والمخصصة ضمن **الإعدادات → نموذج البيانات**.
</Accordion>
<Accordion title="هل يمكنني تغيير أيقونة الكائن؟">
نعم، يمكنك تغيير الأيقونة لكلٍ من الكائنات القياسية والمخصصة ضمن **الإعدادات → نموذج البيانات**.
</Accordion>
<Accordion title="هل يمكنني إعادة ترتيب الكائنات في شريط التنقل الأيسر؟">
ليس بعد. ترتيب الكائنات في التنقل ثابت حاليًا، ولكن هذه الميزة مخطط لها لإصدارات قادمة.
</Accordion>
<Accordion title="هل يمكنني إعادة ترتيب الكائنات في شريط التنقل الأيسر؟">
ليس بعد. ترتيب الكائنات في التنقل ثابت حاليًا، ولكن هذه الميزة مخطط لها لإصدارات قادمة.
</Accordion>
<Accordion title="هل يمكنني إخفاء الكائنات من شريط التنقل الأيسر؟">
تظهر كل الكائنات النشطة في التنقل. يمكنك إلغاء تفعيل الكائنات التي لا تحتاجها تحت **الإعدادات → نموذج البيانات**.
</Accordion>
<Accordion title="هل يمكنني إخفاء الكائنات من شريط التنقل الأيسر؟">
تظهر كل الكائنات النشطة في التنقل. يمكنك إلغاء تفعيل الكائنات التي لا تحتاجها تحت **الإعدادات → نموذج البيانات**.
</Accordion>
</AccordionGroup>
## قدرات الحقول
<AccordionGroup>
<Accordion title="هل يمكنني تغيير نوع الحقل بعد إنشائه؟">
لا، لا يمكن تغيير أنواع الحقول بعد إنشائها. إذا كنت بحاجة إلى نوع مختلف، فأنشئ حقلًا جديدًا بالنوع الصحيح، وانقل بياناتك، ثم عطّل الحقل القديم.
</Accordion>
<Accordion title="هل يمكنني تغيير نوع الحقل بعد إنشائه؟">
لا، لا يمكن تغيير أنواع الحقول بعد إنشائها. إذا كنت بحاجة إلى نوع مختلف، فأنشئ حقلًا جديدًا بالنوع الصحيح، وانقل بياناتك، ثم عطّل الحقل القديم.
</Accordion>
<Accordion title="لماذا أحتاج إلى أسماء مفردة وجمع مختلفة؟">
تستخدم واجهة GraphQL كلا النموذجين لعمليات مختلفة:
* `createPerson` (مفرد) لأعمال سجلات فردية
* `createPeople` (جمع) للعمليات الجماعية
<Accordion title="لماذا أحتاج إلى أسماء مفردة وجمع مختلفة؟">
تستخدم واجهة GraphQL كلا النموذجين لعمليات مختلفة:
* `createPerson` (مفرد) لأعمال سجلات فردية
* `createPeople` (جمع) للعمليات الجماعية
هذا يخلق قيودًا عندما تكون الأشكال المفردة والجمع متشابهة، ولكنه يحسن تجربة المطور.
</Accordion>
هذا يخلق قيودًا عندما تكون الأشكال المفردة والجمع متشابهة، ولكنه يحسن تجربة المطور.
</Accordion>
<Accordion title="لماذا يتم حماية بعض أسماء الحقول؟">
بعض أسماء الحقول مثل `Type` أو `Application` محجوزة لاستخدام النظام. اختر أسماء بديلة مثل `Category` أو `Classification` بدلاً من ذلك.
</Accordion>
<Accordion title="لماذا يتم حماية بعض أسماء الحقول؟">
بعض أسماء الحقول مثل `Type` أو `Application` محجوزة لاستخدام النظام. اختر أسماء بديلة مثل `Category` أو `Classification` بدلاً من ذلك.
</Accordion>
<Accordion title="ماذا يحدث عند إلغاء تنشيط حقل؟">
* يتم إخفاء الحقل من الواجهة
* تظل البيانات محفوظة
* لا يزال بإمكانك الوصول إلى الحقل عبر API
* تظل العلاقات الحالية موجودة، ولكن لا يمكنك إنشاء علاقات جديدة
* يمكنك إعادة تنشيط الحقل لاحقًا
</Accordion>
<Accordion title="ماذا يحدث عند إلغاء تنشيط حقل؟">
* يتم إخفاء الحقل من الواجهة
* تظل البيانات محفوظة
* لا يزال بإمكانك الوصول إلى الحقل عبر API
* تظل العلاقات الحالية موجودة، ولكن لا يمكنك إنشاء علاقات جديدة
* يمكنك إعادة تنشيط الحقل لاحقًا
</Accordion>
<Accordion title="هل يمكنني جعل الحقل إلزاميًا؟">
حاليًا، لا يمكنك جعل الحقول المخصصة إلزامية. جميع الحقول تقبل القيم الفارغة. يمكنك استخدام سير العمل لفرض الحقول الإلزامية عبر إرسال التنبيهات أو حظر الإجراءات عندما تكون الحقول فارغة.
</Accordion>
<Accordion title="هل يمكنني جعل الحقل إلزاميًا؟">
حاليًا، لا يمكنك جعل الحقول المخصصة إلزامية. جميع الحقول تقبل القيم الفارغة. يمكنك استخدام سير العمل لفرض الحقول الإلزامية عبر إرسال التنبيهات أو حظر الإجراءات عندما تكون الحقول فارغة.
</Accordion>
<Accordion title="ما الفرق بين الفريد والإلزامي؟">
* **فريد**: يجب ألّا تتكرر القيمة نفسها في هذا الحقل عبر السجلات
* **إلزامي**: يجب أن يحتوي الحقل على قيمة (غير مدعوم حاليًا للحقول المخصصة)
</Accordion>
<Accordion title="ما الفرق بين الفريد والإلزامي؟">
* **فريد**: يجب ألّا تتكرر القيمة نفسها في هذا الحقل عبر السجلات
* **إلزامي**: يجب أن يحتوي الحقل على قيمة (غير مدعوم حاليًا للحقول المخصصة)
</Accordion>
<Accordion title="هل يمكنني إنشاء حقول صيغة؟">
حقول الصيغ قادمة في **الربع الأول من عام 2026**. في الوقت الحالي، يمكنك استخدام سير العمل لحساب وتحديث قيم الحقول تلقائيًا.
</Accordion>
<Accordion title="هل يمكنني إنشاء حقول صيغة؟">
حقول الصيغ قادمة في **الربع الأول من عام 2026**. في الوقت الحالي، يمكنك استخدام سير العمل لحساب وتحديث قيم الحقول تلقائيًا.
</Accordion>
<Accordion title="هل يمكن أن يكون لدي حقول متداخلة داخل الكائنات الخاصة بي؟">
الحقول المتداخلة قادمة في **الربع الأول من عام 2026**. حاليًا، يمكنك استخدام سير العمل لإحضار قيم الحقول من الكائنات ذات الصلة. مثلاً، لعرض صناعة شركة على سجل شخص، قم بإنشاء حقل مخصص للأشخاص واستخدم سير العمل لمزامنة القيمة.
</Accordion>
<Accordion title="هل يمكن أن يكون لدي حقول متداخلة داخل الكائنات الخاصة بي؟">
الحقول المتداخلة قادمة في **الربع الأول من عام 2026**. حاليًا، يمكنك استخدام سير العمل لإحضار قيم الحقول من الكائنات ذات الصلة. مثلاً، لعرض صناعة شركة على سجل شخص، قم بإنشاء حقل مخصص للأشخاص واستخدم سير العمل لمزامنة القيمة.
</Accordion>
<Accordion title="هل يمكنني إعادة ترتيب الحقول داخل الكائنات؟">
سيكون إعادة ترتيب الحقول متاحًا مع التخطيطات المخصصة في **الربع الرابع من عام 2025**. حاليًا، تظهر الحقول بترتيب أبجدي.
</Accordion>
<Accordion title="هل يمكنني إعادة ترتيب الحقول داخل الكائنات؟">
سيكون إعادة ترتيب الحقول متاحًا مع التخطيطات المخصصة في **الربع الرابع من عام 2025**. حاليًا، تظهر الحقول بترتيب أبجدي.
</Accordion>
</AccordionGroup>
## العلاقات
<AccordionGroup>
<Accordion title="هل يمكنني إنشاء علاقة تشير إلى الكائن نفسه؟">
نعم! العلاقات ذاتية الإشارة مدعومة ومُوصى بها لحالات الاستخدام مثل التسلسلات الهرمية للحسابات. على سبيل المثال، أنشئ علاقة من Companies إلى Companies لتتبع حسابات الأصل/الفرع.
</Accordion>
<Accordion title="هل يمكنني إنشاء علاقة تشير إلى الكائن نفسه؟">
نعم! العلاقات ذاتية الإشارة مدعومة ومُوصى بها لحالات الاستخدام مثل التسلسلات الهرمية للحسابات. على سبيل المثال، أنشئ علاقة من Companies إلى Companies لتتبع حسابات الأصل/الفرع.
</Accordion>
<Accordion title="هل يمكنني إنشاء علاقات عديدة-إلى-عدة؟">
العلاقات عديدة-إلى-عديدة قادمة في **النصف الأول من عام 2026**. حاليًا، أنشئ كائنًا وسيطًا مع علاقتين واحد-إلى-عديد كحل بديل.
<Accordion title="هل يمكنني إنشاء علاقات عديدة-إلى-عدة؟">
العلاقات عديدة-إلى-عديدة قادمة في **النصف الأول من عام 2026**. حاليًا، أنشئ كائنًا وسيطًا مع علاقتين واحد-إلى-عديد كحل بديل.
على سبيل المثال، لربط People وProjects (عديد-إلى-عديد)، أنشئ كائنًا باسم "Project Assignments" يحتوي على:
على سبيل المثال، لربط People وProjects (عديد-إلى-عديد)، أنشئ كائنًا باسم "Project Assignments" يحتوي على:
* علاقة إلى People (عديد من التعيينات → شخص واحد)
* علاقة إلى Projects (عديد من التعيينات → مشروع واحد)
</Accordion>
* علاقة إلى People (عديد من التعيينات → شخص واحد)
* علاقة إلى Projects (عديد من التعيينات → مشروع واحد)
</Accordion>
<Accordion title="ما هي العلاقات إلى أنواع كائنات متعددة (علاقات Morph)؟">
تتيح هذه أن يرتبط كائن واحد بأنواع كائنات متعددة مختلفة عبر حقل واحد. على سبيل المثال، يمكن إرفاق Notes بـ People وCompanies وOpportunities في الوقت نفسه.
<Accordion title="ما هي العلاقات إلى أنواع كائنات متعددة (علاقات Morph)؟">
تتيح هذه أن يرتبط كائن واحد بأنواع كائنات متعددة مختلفة عبر حقل واحد. على سبيل المثال، يمكن إرفاق Notes بـ People وCompanies وOpportunities في الوقت نفسه.
يرتبط كل Note بشخص واحد وشركة واحدة وفرصة واحدة في الوقت نفسه.
يرتبط كل Note بشخص واحد وشركة واحدة وفرصة واحدة في الوقت نفسه.
اعرف المزيد في [حقول العلاقات](/l/ar/user-guide/data-model/capabilities/relation-fields).
</Accordion>
اعرف المزيد في [حقول العلاقات](/l/ar/user-guide/data-model/capabilities/relation-fields).
</Accordion>
<Accordion title="هل يمكنني إنشاء علاقات متعددة بين الكائنات نفسها؟">
نعم، يمكنك إنشاء علاقات متعددة بين الكائنين نفسيهما. على سبيل المثال، يمكن أن يكون لدى Company علاقتا "Primary Contact" و"Billing Contact" مع People.
</Accordion>
<Accordion title="هل يمكنني إنشاء علاقات متعددة بين الكائنات نفسها؟">
نعم، يمكنك إنشاء علاقات متعددة بين الكائنين نفسيهما. على سبيل المثال، يمكن أن يكون لدى Company علاقتا "Primary Contact" و"Billing Contact" مع People.
</Accordion>
<Accordion title="ماذا يحدث للعلاقات عند حذف سجل؟">
عند حذف سجل، تتم إزالة رابط العلاقة من السجلات المرتبطة. السجلات المرتبطة نفسها لا تُحذف.
</Accordion>
<Accordion title="ماذا يحدث للعلاقات عند حذف سجل؟">
عند حذف سجل، تتم إزالة رابط العلاقة من السجلات المرتبطة. السجلات المرتبطة نفسها لا تُحذف.
</Accordion>
<Accordion title="هل يمكنني إنشاء علاقات دائرية؟">
على الرغم من أنها ممكنة تقنيًا، يجب تجنّب العلاقات الدائرية (A → B → C → A) لأنها قد تسبب التباسًا ومشكلات محتملة في الأداء.
</Accordion>
<Accordion title="هل يمكنني إنشاء علاقات دائرية؟">
على الرغم من أنها ممكنة تقنيًا، يجب تجنّب العلاقات الدائرية (A → B → C → A) لأنها قد تسبب التباسًا ومشكلات محتملة في الأداء.
</Accordion>
</AccordionGroup>
## الوصول والصلاحيات
<AccordionGroup>
<Accordion title="أين يمكنني رؤية وتعديل نموذج البيانات الخاص بي؟">
انتقل إلى **الإعدادات → نموذج البيانات** لعرض جميع الكائنات والحقول الخاصة بك وتحريرها.
</Accordion>
<Accordion title="أين يمكنني رؤية وتعديل نموذج البيانات الخاص بي؟">
انتقل إلى **الإعدادات → نموذج البيانات** لعرض جميع الكائنات والحقول الخاصة بك وتحريرها.
</Accordion>
<Accordion title="لماذا لا أستطيع رؤية نموذج البيانات تحت الإعدادات؟">
اتصل بمسؤول المساحة لديك. يكون الوصول إلى نموذج البيانات عادة محظورًا على المسؤولين فقط.
</Accordion>
<Accordion title="لماذا لا أستطيع رؤية نموذج البيانات تحت الإعدادات؟">
اتصل بمسؤول المساحة لديك. يكون الوصول إلى نموذج البيانات عادة محظورًا على المسؤولين فقط.
</Accordion>
</AccordionGroup>
## إدارة البيانات
<AccordionGroup>
<Accordion title="هل هناك حد لعدد السجلات التي يمكنني امتلاكها؟">
لا يوجد حد صارم لعدد السجلات. ومع ذلك، قد تؤثر مجموعات البيانات الكبيرة جدًا في الأداء في بعض طرق العرض. استخدم عوامل التصفية وطرق العرض لإدارة مجموعات البيانات الكبيرة بفعالية.
</Accordion>
<Accordion title="هل هناك حد لعدد السجلات التي يمكنني امتلاكها؟">
لا يوجد حد صارم لعدد السجلات. ومع ذلك، قد تؤثر مجموعات البيانات الكبيرة جدًا في الأداء في بعض طرق العرض. استخدم عوامل التصفية وطرق العرض لإدارة مجموعات البيانات الكبيرة بفعالية.
</Accordion>
<Accordion title="هل يمكنني استيراد بيانات إلى الكائنات المخصصة؟">
نعم، يمكنك استيراد بيانات CSV إلى أي كائن، بما في ذلك الكائنات المخصصة. تدعم عملية الاستيراد تعيين الحقول للحقول المخصصة. اطّلع على [كيفية إعداد ملفات CSV الخاصة بك](/l/ar/user-guide/data-migration/how-tos/prepare-your-csv-files).
</Accordion>
<Accordion title="هل يمكنني استيراد بيانات إلى الكائنات المخصصة؟">
نعم، يمكنك استيراد بيانات CSV إلى أي كائن، بما في ذلك الكائنات المخصصة. تدعم عملية الاستيراد تعيين الحقول للحقول المخصصة. اطّلع على [كيفية إعداد ملفات CSV الخاصة بك](/l/ar/user-guide/data-migration/how-tos/prepare-your-csv-files).
</Accordion>
<Accordion title="هل يمكنني تصدير تكوين نموذج البيانات الخاص بي؟">
حاليًا، لا يوجد تصدير مدمج لتكوين نموذج البيانات. تواصل مع الدعم إذا كنت بحاجة إلى نقل نموذج البيانات بين مساحات العمل.
</Accordion>
<Accordion title="هل يمكنني تصدير تكوين نموذج البيانات الخاص بي؟">
حاليًا، لا يوجد تصدير مدمج لتكوين نموذج البيانات. تواصل مع الدعم إذا كنت بحاجة إلى نقل نموذج البيانات بين مساحات العمل.
</Accordion>
</AccordionGroup>
## هل تحتاج إلى المزيد من المساعدة؟

Some files were not shown because too many files have changed in this diff Show More