Compare commits
150
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3311ca1a58 | ||
|
|
718a57172a | ||
|
|
cb128c4158 | ||
|
|
0288545d7b | ||
|
|
996947a3cb | ||
|
|
05917e23a9 | ||
|
|
3f0cfe66d1 | ||
|
|
30dc70a943 | ||
|
|
7221b45ff7 | ||
|
|
49d05f3ca7 | ||
|
|
0e44e07fcf | ||
|
|
9fe2a07c55 | ||
|
|
63f17eec2c | ||
|
|
e806d36099 | ||
|
|
9f5a8735c9 | ||
|
|
c0e6aa1c0b | ||
|
|
9342b16aad | ||
|
|
cfad24da48 | ||
|
|
86fbf69e95 | ||
|
|
9667b3f369 | ||
|
|
76c7639eb3 | ||
|
|
4ed09a3feb | ||
|
|
def5ea5764 | ||
|
|
81698ff32c | ||
|
|
c0c51f2ef5 | ||
|
|
3bbaff801a | ||
|
|
2d2fc06265 | ||
|
|
8a7a19f312 | ||
|
|
2f0103faa5 | ||
|
|
08bfbfda45 | ||
|
|
c025a0c2b8 | ||
|
|
5e19361494 | ||
|
|
f11d76d6cf | ||
|
|
3aedce9af7 | ||
|
|
8e8ecfb8a3 | ||
|
|
120096346a | ||
|
|
fde8168a85 | ||
|
|
2674589b44 | ||
|
|
f325509d96 | ||
|
|
a9649b06d5 | ||
|
|
0b4bf97f35 | ||
|
|
4ea8245387 | ||
|
|
0fa8063054 | ||
|
|
546114d07f | ||
|
|
92824c6533 | ||
|
|
bc4ae35bc4 | ||
|
|
1b805a36f3 | ||
|
|
f7ea1d9500 | ||
|
|
cbb0f212cf | ||
|
|
0af980a783 | ||
|
|
e01b641a05 | ||
|
|
50be97422d | ||
|
|
4ae8381ffc | ||
|
|
856859f9f3 | ||
|
|
98d67e0f6b | ||
|
|
1fa55bfd02 | ||
|
|
001c2097a3 | ||
|
|
9107f5bbc7 | ||
|
|
435e21d23f | ||
|
|
448241540d | ||
|
|
e7f958c9cf | ||
|
|
98e73791a4 | ||
|
|
f3857c41b8 | ||
|
|
2c02f2c499 | ||
|
|
121788c42f | ||
|
|
ecb7270a7b | ||
|
|
e365b546d9 | ||
|
|
f080b952eb | ||
|
|
b84a8588be | ||
|
|
e6a415b438 | ||
|
|
d81f006979 | ||
|
|
eaf9fe27b2 | ||
|
|
b339c93699 | ||
|
|
887371054a | ||
|
|
b56f85f36a | ||
|
|
1ed13182d0 | ||
|
|
20d997bc5a | ||
|
|
0f2af6a6cb | ||
|
|
c6ec764b23 | ||
|
|
959cbfb725 | ||
|
|
458a61ed30 | ||
|
|
9a3852bf04 | ||
|
|
c369baf63a | ||
|
|
9e8024a07a | ||
|
|
df196b4a03 | ||
|
|
af0af0a237 | ||
|
|
0d4fe4575b | ||
|
|
ccddd105d8 | ||
|
|
e21a7e11b2 | ||
|
|
1ac87d106e | ||
|
|
9a0295fd3d | ||
|
|
a0c0e7de25 | ||
|
|
7cbbd082e3 | ||
|
|
7944ca29fa | ||
|
|
b51eb6471f | ||
|
|
22bc3004b9 | ||
|
|
129d1ede86 | ||
|
|
a2e3af5625 | ||
|
|
41f09c5a4c | ||
|
|
a900b4a4b4 | ||
|
|
020cf7f3e0 | ||
|
|
0bccdedb4f | ||
|
|
088be4c47b | ||
|
|
ad88108b28 | ||
|
|
58e8b466f3 | ||
|
|
ba59ab8094 | ||
|
|
cd1a2712d5 | ||
|
|
932adf8e4b | ||
|
|
26fabdc3d0 | ||
|
|
82617727a2 | ||
|
|
87922253f3 | ||
|
|
7da8450075 | ||
|
|
d444648cc0 | ||
|
|
ec57d3fe0f | ||
|
|
00209f7e2c | ||
|
|
b107020ad3 | ||
|
|
9f3f2e21d8 | ||
|
|
21472da38f | ||
|
|
bf1e0d0811 | ||
|
|
917a36821a | ||
|
|
66da296799 | ||
|
|
3bc887e12e | ||
|
|
d060c843d1 | ||
|
|
175df59c21 | ||
|
|
6ad581d178 | ||
|
|
712b1553bd | ||
|
|
cc1145c701 | ||
|
|
726b1373d9 | ||
|
|
783e57f5d8 | ||
|
|
163254baef | ||
|
|
aaa6511007 | ||
|
|
2a670520b9 | ||
|
|
10bd005021 | ||
|
|
0e25aeb5be | ||
|
|
e051cce24f | ||
|
|
bd03073b6d | ||
|
|
b3d8f8813f | ||
|
|
37d9828458 | ||
|
|
e16b6d6485 | ||
|
|
9626c7ce02 | ||
|
|
754de411fe | ||
|
|
530f74e28c | ||
|
|
5a46cf7bd3 | ||
|
|
7b10202c69 | ||
|
|
9e54a8082c | ||
|
|
1a13258302 | ||
|
|
c0ea049ad7 | ||
|
|
b33f86fbf5 | ||
|
|
88424611ec | ||
|
|
082400f751 |
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
deploy-main:
|
||||
timeout-minutes: 3
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
deploy-tag:
|
||||
timeout-minutes: 3
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Repository Dispatch
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
|
||||
@@ -16,7 +16,7 @@ permissions:
|
||||
jobs:
|
||||
changed-files:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
outputs:
|
||||
any_changed: ${{ steps.changed-files.outputs.any_changed }}
|
||||
steps:
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 45
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
ci-create-app-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check, create-app-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
|
||||
@@ -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
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
ci-emails-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check, emails-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -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: ubuntu-latest
|
||||
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 }}
|
||||
@@ -169,8 +169,9 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
TASK_CACHE_KEY: front-task-${{ matrix.task }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -202,14 +203,6 @@ jobs:
|
||||
with:
|
||||
tag: scope:frontend
|
||||
tasks: ${{ matrix.task }}
|
||||
- name: Check for coverage threshold failure
|
||||
if: always() && steps.run-task.outcome == 'failure' && matrix.task == 'test'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::error::The test task failed. If no individual test is failing, this is likely a coverage threshold not being met."
|
||||
echo ""
|
||||
echo "To debug locally, run: npx nx run twenty-front:test:ci"
|
||||
exit 1
|
||||
- name: Save ${{ matrix.task }} cache
|
||||
uses: ./.github/actions/save-cache
|
||||
with:
|
||||
@@ -218,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:
|
||||
@@ -236,14 +229,14 @@ 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: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check-e2e, front-build]
|
||||
if: |
|
||||
always() &&
|
||||
@@ -303,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
|
||||
@@ -343,23 +339,23 @@ 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()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
front-task,
|
||||
front-build,
|
||||
merge-reports-and-check-coverage,
|
||||
# merge-reports-and-check-coverage,
|
||||
front-sb-test,
|
||||
front-sb-build,
|
||||
]
|
||||
@@ -370,7 +366,7 @@ jobs:
|
||||
ci-e2e-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check-e2e, e2e-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -25,7 +25,7 @@ defaults:
|
||||
jobs:
|
||||
create_pr:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -15,7 +15,7 @@ defaults:
|
||||
jobs:
|
||||
tag_and_release:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release')
|
||||
steps:
|
||||
- name: Check PR Author
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
|
||||
@@ -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:
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
ci-sdk-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check, sdk-test, sdk-e2e-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -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
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
ci-server-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check, server-setup, server-test, server-integration-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -22,7 +22,9 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test]
|
||||
@@ -45,7 +47,7 @@ jobs:
|
||||
ci-shared-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check, shared-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
ci-test-docker-compose-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check, test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -25,7 +25,7 @@ concurrency:
|
||||
jobs:
|
||||
danger-js:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
if: github.event.action != 'closed'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
congratulate:
|
||||
timeout-minutes: 3
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
services:
|
||||
postgres:
|
||||
image: twentycrm/twenty-postgres-spilo
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
ci-website-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
needs: [changed-files-check, website-build]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
|
||||
@@ -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
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && github.event.comment.user.type != 'Bot') ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && github.event.review.user.type != 'Bot') ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
|
||||
claude-cross-repo:
|
||||
if: github.event_name == 'repository_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -34,7 +34,7 @@ concurrency:
|
||||
jobs:
|
||||
pull_docs_translations:
|
||||
name: Pull docs translations
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -21,7 +21,7 @@ concurrency:
|
||||
jobs:
|
||||
push_docs:
|
||||
name: Push documentation to Crowdin
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -32,7 +32,7 @@ concurrency:
|
||||
jobs:
|
||||
pull_translations:
|
||||
name: Pull translations
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -17,7 +17,7 @@ concurrency:
|
||||
jobs:
|
||||
extract_translations:
|
||||
name: Extract and upload translations
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -18,7 +18,7 @@ concurrency:
|
||||
jobs:
|
||||
qa_report:
|
||||
name: Generate QA Report
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
trigger-preview:
|
||||
if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || (github.event.action == 'labeled' && github.event.label.name == 'preview-app')
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-24.04
|
||||
steps:
|
||||
- name: Trigger preview environment workflow
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.client_payload.pr_head_sha }}
|
||||
|
||||
|
||||
- name: Run compose setup
|
||||
run: |
|
||||
echo "Patching docker-compose.yml..."
|
||||
@@ -25,17 +25,17 @@ jobs:
|
||||
yq eval 'del(.services.server.image)' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.server.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
|
||||
|
||||
|
||||
yq eval 'del(.services.worker.image)' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.worker.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
|
||||
yq eval '.services.worker.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
|
||||
|
||||
|
||||
echo "Adding SIGN_IN_PREFILLED environment variable to server service..."
|
||||
yq eval '.services.server.environment.SIGN_IN_PREFILLED = "${SIGN_IN_PREFILLED}"' -i packages/twenty-docker/docker-compose.yml
|
||||
|
||||
|
||||
echo "Setting up .env file..."
|
||||
cp packages/twenty-docker/.env.example packages/twenty-docker/.env
|
||||
|
||||
|
||||
echo "Generating secrets..."
|
||||
echo "" >> packages/twenty-docker/.env
|
||||
echo "# === Randomly generated secrets ===" >> packages/twenty-docker/.env
|
||||
@@ -46,24 +46,24 @@ jobs:
|
||||
cd packages/twenty-docker/
|
||||
docker compose build
|
||||
working-directory: ./
|
||||
|
||||
|
||||
- name: Create Tunnel
|
||||
id: expose-tunnel
|
||||
uses: codetalkio/expose-tunnel@v1.5.0
|
||||
with:
|
||||
service: bore.pub
|
||||
port: 3000
|
||||
|
||||
|
||||
- name: Start services with correct SERVER_URL
|
||||
run: |
|
||||
cd packages/twenty-docker/
|
||||
|
||||
|
||||
# Update the SERVER_URL with the tunnel URL
|
||||
echo "Setting SERVER_URL to ${{ steps.expose-tunnel.outputs.tunnel-url }}"
|
||||
sed -i '/SERVER_URL=/d' .env
|
||||
echo "" >> .env
|
||||
echo "SERVER_URL=${{ steps.expose-tunnel.outputs.tunnel-url }}" >> .env
|
||||
|
||||
|
||||
# Start the services
|
||||
echo "Docker compose up..."
|
||||
docker compose up -d || {
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
docker compose logs
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
echo "Waiting for services to be ready..."
|
||||
count=0
|
||||
while [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-db-1) = "healthy" ] || [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-server-1) = "healthy" ]; do
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
fi
|
||||
echo "Still waiting for services... ($count/60)"
|
||||
done
|
||||
|
||||
|
||||
echo "All services are up and running!"
|
||||
working-directory: ./
|
||||
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
echo "✅ Preview Environment Ready!"
|
||||
echo "🔗 Preview URL: ${{ steps.expose-tunnel.outputs.tunnel-url }}"
|
||||
echo "⏱️ This environment will be available for 5 hours"
|
||||
|
||||
|
||||
- name: Post comment on PR
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
@@ -113,21 +113,21 @@ jobs:
|
||||
const COMMENT_MARKER = '<!-- PR_PREVIEW_ENV -->';
|
||||
const commentBody = `${COMMENT_MARKER}
|
||||
🚀 **Preview Environment Ready!**
|
||||
|
||||
|
||||
Your preview environment is available at: ${{ steps.expose-tunnel.outputs.tunnel-url }}
|
||||
|
||||
|
||||
This environment will automatically shut down when the PR is closed or after 5 hours.`;
|
||||
|
||||
|
||||
// Get all comments
|
||||
const {data: comments} = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: ${{ github.event.client_payload.pr_number }},
|
||||
});
|
||||
|
||||
|
||||
// Find our comment
|
||||
const botComment = comments.find(comment => comment.body.includes(COMMENT_MARKER));
|
||||
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
@@ -147,13 +147,13 @@ jobs:
|
||||
});
|
||||
console.log('Created new comment');
|
||||
}
|
||||
|
||||
|
||||
- name: Keep tunnel alive for 5 hours
|
||||
run: timeout 300m sleep 18000 # Stop on whichever we reach first (300m or 5hour sleep)
|
||||
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
cd packages/twenty-docker/
|
||||
docker compose down -v
|
||||
working-directory: ./
|
||||
working-directory: ./
|
||||
|
||||
@@ -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)`
|
||||
|
||||
@@ -28,7 +28,7 @@ See:
|
||||
🚀 [Self-hosting](https://docs.twenty.com/developers/self-hosting/docker-compose)
|
||||
🖥️ [Local Setup](https://docs.twenty.com/developers/local-setup)
|
||||
|
||||
# Does the world need another CRM?
|
||||
# Why Twenty
|
||||
|
||||
We built Twenty for three reasons:
|
||||
|
||||
@@ -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/)
|
||||
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@ Below are a few features we have implemented to date:
|
||||
<a href="https://greptile.com"><img src="./packages/twenty-website/public/images/readme/greptile.png" height="30" alt="Greptile" /></a>
|
||||
<a href="https://sentry.io/"><img src="./packages/twenty-website/public/images/readme/sentry.png" height="30" alt="Sentry" /></a>
|
||||
<a href="https://crowdin.com/"><img src="./packages/twenty-website/public/images/readme/crowdin.png" height="30" alt="Crowdin" /></a>
|
||||
<a href="https://e2b.dev/"><img src="./packages/twenty-website/public/images/readme/e2b.svg" height="30" alt="E2B" /></a>
|
||||
</p>
|
||||
|
||||
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
|
||||
|
||||
@@ -83,6 +83,10 @@ export default [
|
||||
sourceTag: 'scope:frontend',
|
||||
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
|
||||
},
|
||||
{
|
||||
sourceTag: 'scope:zapier',
|
||||
onlyDependOnLibsWithTags: ['scope:shared', 'scope:zapier'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"maxWorkers": 3
|
||||
"maxWorkers": 1
|
||||
},
|
||||
"coverage": {
|
||||
"coverageReporters": ["lcov", "text"]
|
||||
|
||||
+12
-15
@@ -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,17 +83,17 @@
|
||||
"@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.8.0",
|
||||
"@swc-node/register": "1.11.1",
|
||||
"@swc/cli": "^0.3.12",
|
||||
"@swc/core": "1.13.3",
|
||||
"@swc/helpers": "~0.5.2",
|
||||
"@swc/core": "1.15.11",
|
||||
"@swc/helpers": "~0.5.18",
|
||||
"@swc/jest": "^0.2.39",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
@@ -137,7 +136,7 @@
|
||||
"@typescript-eslint/parser": "^8.39.0",
|
||||
"@typescript-eslint/utils": "^8.39.0",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
|
||||
"@vitejs/plugin-react-swc": "3.11.0",
|
||||
"@vitejs/plugin-react-swc": "4.2.3",
|
||||
"@vitest/browser-playwright": "^4.0.18",
|
||||
"@vitest/coverage-istanbul": "^4.0.18",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -89,12 +88,14 @@ In interactive mode, you can pick from:
|
||||
- **Example front component** — a React UI component (`front-components/hello-world.tsx`)
|
||||
- **Example view** — a saved view for the example object (`views/example-view.ts`)
|
||||
- **Example navigation menu item** — a sidebar link (`navigation-menu-items/example-navigation-menu-item.ts`)
|
||||
- **Example skill** — an AI agent skill definition (`skills/example-skill.ts`)
|
||||
|
||||
## What gets scaffolded
|
||||
|
||||
**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
|
||||
@@ -106,13 +107,14 @@ In interactive mode, you can pick from:
|
||||
- `front-components/hello-world.tsx` — Example front component
|
||||
- `views/example-view.ts` — Example saved view for the example object
|
||||
- `navigation-menu-items/example-navigation-menu-item.ts` — Example sidebar navigation link
|
||||
- `skills/example-skill.ts` — Example AI agent skill definition
|
||||
|
||||
## Next steps
|
||||
- Run `yarn twenty help` to see all available commands.
|
||||
- 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).
|
||||
- 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 auto‑generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`.
|
||||
- Two typed API clients are auto‑generated 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,5 +1,5 @@
|
||||
const jestConfig = {
|
||||
displayName: 'twenty-cli',
|
||||
displayName: 'create-twenty-app',
|
||||
preset: '../../jest.preset.js',
|
||||
testEnvironment: 'node',
|
||||
transformIgnorePatterns: ['../../node_modules/'],
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
## Base documentation
|
||||
|
||||
- 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.
|
||||
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
|
||||
@@ -36,6 +36,10 @@ yarn twenty function:execute # Execute a function with JSON payload
|
||||
yarn twenty app:uninstall # Uninstall app from workspace
|
||||
```
|
||||
|
||||
## LLMs instructions
|
||||
|
||||
Main docs and pitfalls are available in LLMS.md file.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Twenty applications, take a look at the following resources:
|
||||
|
||||
@@ -114,6 +114,7 @@ export class CreateAppCommand {
|
||||
includeExampleFrontComponent: false,
|
||||
includeExampleView: false,
|
||||
includeExampleNavigationMenuItem: false,
|
||||
includeExampleSkill: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,6 +126,7 @@ export class CreateAppCommand {
|
||||
includeExampleFrontComponent: true,
|
||||
includeExampleView: true,
|
||||
includeExampleNavigationMenuItem: true,
|
||||
includeExampleSkill: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -164,6 +166,11 @@ export class CreateAppCommand {
|
||||
value: 'navigationMenuItem',
|
||||
checked: true,
|
||||
},
|
||||
{
|
||||
name: 'Example skill (AI agent skill definition)',
|
||||
value: 'skill',
|
||||
checked: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
@@ -189,6 +196,7 @@ export class CreateAppCommand {
|
||||
includeExampleView: includeView,
|
||||
includeExampleNavigationMenuItem:
|
||||
selectedExamples.includes('navigationMenuItem'),
|
||||
includeExampleSkill: selectedExamples.includes('skill'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ export type ExampleOptions = {
|
||||
includeExampleFrontComponent: boolean;
|
||||
includeExampleView: boolean;
|
||||
includeExampleNavigationMenuItem: boolean;
|
||||
includeExampleSkill: boolean;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import { copyBaseApplicationProject } from '@/utils/app-template';
|
||||
import { type ExampleOptions } from '@/types/scaffolding-options';
|
||||
import { GENERATED_DIR } from 'twenty-shared/application';
|
||||
import { copyBaseApplicationProject } from '@/utils/app-template';
|
||||
import * as fs from 'fs-extra';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
// Mock fs-extra's copy function to skip copying base template (not available during tests)
|
||||
jest.mock('fs-extra', () => {
|
||||
@@ -23,11 +24,13 @@ const ALL_EXAMPLES: ExampleOptions = {
|
||||
includeExampleFrontComponent: true,
|
||||
includeExampleView: true,
|
||||
includeExampleNavigationMenuItem: true,
|
||||
includeExampleSkill: true,
|
||||
};
|
||||
|
||||
const NO_EXAMPLES: ExampleOptions = {
|
||||
includeExampleObject: false,
|
||||
includeExampleField: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleLogicFunction: false,
|
||||
includeExampleFrontComponent: false,
|
||||
includeExampleView: false,
|
||||
@@ -109,7 +112,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
|
||||
const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
|
||||
expect(gitignoreContent).toContain('/node_modules');
|
||||
expect(gitignoreContent).toContain('generated');
|
||||
expect(gitignoreContent).toContain(GENERATED_DIR);
|
||||
});
|
||||
|
||||
it('should create yarn.lock file', async () => {
|
||||
@@ -372,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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -395,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')),
|
||||
@@ -437,6 +464,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
exampleOptions: {
|
||||
includeExampleObject: false,
|
||||
includeExampleField: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleLogicFunction: false,
|
||||
includeExampleFrontComponent: true,
|
||||
includeExampleView: false,
|
||||
@@ -472,6 +500,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
appDirectory: testAppDirectory,
|
||||
exampleOptions: {
|
||||
includeExampleObject: false,
|
||||
includeExampleSkill: false,
|
||||
includeExampleField: false,
|
||||
includeExampleLogicFunction: true,
|
||||
includeExampleFrontComponent: false,
|
||||
@@ -671,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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,6 +89,20 @@ export const copyBaseApplicationProject = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (exampleOptions.includeExampleSkill) {
|
||||
await createExampleSkill({
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'skills',
|
||||
fileName: 'example-skill.ts',
|
||||
});
|
||||
}
|
||||
|
||||
await createDefaultPreInstallFunction({
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'logic-functions',
|
||||
fileName: 'pre-install.ts',
|
||||
});
|
||||
|
||||
await createDefaultPostInstallFunction({
|
||||
appDirectory: sourceFolderPath,
|
||||
fileFolder: 'logic-functions',
|
||||
@@ -260,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,
|
||||
@@ -271,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,
|
||||
@@ -424,6 +466,36 @@ export default defineNavigationMenuItem({
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createExampleSkill = async ({
|
||||
appDirectory,
|
||||
fileFolder,
|
||||
fileName,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
fileFolder?: string;
|
||||
fileName: string;
|
||||
}) => {
|
||||
const universalIdentifier = v4();
|
||||
|
||||
const content = `import { defineSkill } from 'twenty-sdk';
|
||||
|
||||
export const EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER =
|
||||
'${universalIdentifier}';
|
||||
|
||||
export default defineSkill({
|
||||
universalIdentifier: EXAMPLE_SKILL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'example-skill',
|
||||
label: 'Example Skill',
|
||||
description: 'A sample skill for your application',
|
||||
icon: 'IconBrain',
|
||||
content: 'Add your skill instructions here. Skills provide context and capabilities to AI agents.',
|
||||
});
|
||||
`;
|
||||
|
||||
await fs.ensureDir(join(appDirectory, fileFolder ?? ''));
|
||||
await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content);
|
||||
};
|
||||
|
||||
const createApplicationConfig = async ({
|
||||
displayName,
|
||||
description,
|
||||
@@ -439,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);
|
||||
}
|
||||
|
||||
+19
-18
@@ -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.
|
||||
|
||||
|
||||
+2
-2
@@ -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.
|
||||
|
||||
|
||||
+2
-2
@@ -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.
|
||||
|
||||
|
||||
+3
-3
@@ -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.
|
||||
|
||||
@@ -14,6 +14,7 @@ Apps let you build and manage Twenty customizations **as code**. Instead of conf
|
||||
**What you can do today:**
|
||||
- Define custom objects and fields as code (managed data model)
|
||||
- Build logic functions with custom triggers
|
||||
- Define skills for AI agents
|
||||
- Deploy the same app across multiple workspaces
|
||||
|
||||
## Prerequisites
|
||||
@@ -30,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
|
||||
```
|
||||
@@ -44,7 +38,7 @@ yarn twenty app:dev
|
||||
The scaffolder supports three modes for controlling which example files are included:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item)
|
||||
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# Minimal: only core files (application-config.ts and default-role.ts)
|
||||
@@ -66,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
|
||||
|
||||
@@ -85,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:
|
||||
|
||||
@@ -112,16 +109,19 @@ 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
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
└── navigation-menu-items/
|
||||
└── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── 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:
|
||||
|
||||
@@ -142,11 +142,14 @@ 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 |
|
||||
| `defineView()` | Saved view definitions |
|
||||
| `defineNavigationMenuItem()` | Navigation menu item definitions |
|
||||
| `defineSkill()` | AI agent skill definitions |
|
||||
|
||||
<Note>
|
||||
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
|
||||
@@ -166,8 +169,8 @@ 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 entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, or roles.
|
||||
- `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
|
||||
|
||||
@@ -215,11 +218,14 @@ 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 |
|
||||
| `defineView()` | Define saved views for objects |
|
||||
| `defineNavigationMenuItem()` | Define sidebar navigation links |
|
||||
| `defineSkill()` | Define AI agent skills |
|
||||
|
||||
These functions validate your configuration at build time and provide IDE autocompletion and type safety.
|
||||
|
||||
@@ -320,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**: key–value 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:
|
||||
@@ -328,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',
|
||||
@@ -344,7 +350,6 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -352,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
|
||||
|
||||
@@ -425,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';
|
||||
@@ -485,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.
|
||||
@@ -493,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,
|
||||
@@ -510,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"
|
||||
@@ -528,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`.
|
||||
|
||||
@@ -637,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: {
|
||||
@@ -698,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 = () => {
|
||||
@@ -720,27 +751,66 @@ 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.
|
||||
|
||||
### Generated typed client
|
||||
### Skills
|
||||
|
||||
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:
|
||||
Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation:
|
||||
|
||||
```typescript
|
||||
import Twenty from '~/generated';
|
||||
// src/skills/example-skill.ts
|
||||
import { defineSkill } from 'twenty-sdk';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
export default defineSkill({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'sales-outreach',
|
||||
label: 'Sales Outreach',
|
||||
description: 'Guides the AI agent through a structured sales outreach process',
|
||||
icon: 'IconBrain',
|
||||
content: `You are a sales outreach assistant. When reaching out to a prospect:
|
||||
1. Research the company and recent news
|
||||
2. Identify the prospect's role and likely pain points
|
||||
3. Draft a personalized message referencing specific details
|
||||
4. Keep the tone professional but conversational`,
|
||||
});
|
||||
```
|
||||
|
||||
The client is re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
Key points:
|
||||
- `name` is a unique identifier string for the skill (kebab-case recommended).
|
||||
- `label` is the human-readable display name shown in the UI.
|
||||
- `content` contains the skill instructions — this is the text the AI agent uses.
|
||||
- `icon` (optional) sets the icon displayed in the UI.
|
||||
- `description` (optional) provides additional context about the skill's purpose.
|
||||
|
||||
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 clients
|
||||
|
||||
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 { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
|
||||
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 } });
|
||||
```
|
||||
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Runtime credentials in logic functions
|
||||
|
||||
@@ -754,6 +824,51 @@ Notes:
|
||||
- The API key's permissions are determined by the role referenced in your `application-config.ts` via `defaultRoleUniversalIdentifier`. This is the default role used by logic functions of your application.
|
||||
- Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `defaultRoleUniversalIdentifier` to that role's universal identifier.
|
||||
|
||||
#### Uploading files
|
||||
|
||||
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 { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
const fileBuffer = fs.readFileSync('./invoice.pdf');
|
||||
|
||||
const uploadedFile = await metadataClient.uploadFile(
|
||||
fileBuffer, // file contents as a Buffer
|
||||
'invoice.pdf', // filename
|
||||
'application/pdf', // MIME type (defaults to 'application/octet-stream')
|
||||
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier
|
||||
);
|
||||
|
||||
console.log(uploadedFile);
|
||||
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
||||
```
|
||||
|
||||
The method signature:
|
||||
|
||||
```typescript
|
||||
uploadFile(
|
||||
fileBuffer: Buffer,
|
||||
filename: string,
|
||||
contentType: string,
|
||||
fieldMetadataUniversalIdentifier: string,
|
||||
): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }>
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `fileBuffer` | `Buffer` | The raw file contents |
|
||||
| `filename` | `string` | The name of the file (used for storage and display) |
|
||||
| `contentType` | `string` | MIME type of the file (defaults to `application/octet-stream` if omitted) |
|
||||
| `fieldMetadataUniversalIdentifier` | `string` | The `universalIdentifier` of the file-type field on your object |
|
||||
|
||||
Key points:
|
||||
- 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.
|
||||
|
||||
### Hello World example
|
||||
|
||||
|
||||
+3
-3
@@ -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>
|
||||
|
||||
+4
-2
@@ -60,9 +60,11 @@ npx nx run twenty-server:command workspace:sync-metadata -f
|
||||
```
|
||||
|
||||
<Warning>
|
||||
سيؤدي هذا إلى إسقاط قاعدة البيانات وإعادة تشغيل الهجرات والبذور.
|
||||
|
||||
تأكد من عمل نسخة احتياطية لأي بيانات تريد الاحتفاظ بها قبل تشغيل هذا الأمر.
|
||||
سيؤدي هذا إلى إسقاط قاعدة البيانات وإعادة تشغيل الهجرات والبذور.
|
||||
|
||||
تأكد من عمل نسخة احتياطية لأي بيانات تريد الاحتفاظ بها قبل تشغيل هذا الأمر.
|
||||
|
||||
</Warning>
|
||||
|
||||
## "التقنية المستخدمة"
|
||||
|
||||
+3
-1
@@ -43,7 +43,9 @@ cp .env.example .env
|
||||
## التطوير
|
||||
|
||||
<Warning>
|
||||
تأكد من تشغيل `yarn build` قبل أي أمر `zapier`.
|
||||
|
||||
تأكد من تشغيل `yarn build` قبل أي أمر `zapier`.
|
||||
|
||||
</Warning>
|
||||
|
||||
### تجربة
|
||||
|
||||
+23
-20
@@ -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 والمحددات تعتبر طريقة رائعة لتجنب إعادة العرض.
|
||||
تُعد حالات عائلة الذرات والمحددات طريقة رائعة لتجنّب عمليات إعادة التصيير.
|
||||
|
||||
إنها مفيدة عندما تحتاج إلى تخزين قائمة من العناصر.
|
||||
|
||||
|
||||
+2
-2
@@ -82,9 +82,9 @@ module1
|
||||
|
||||
### الحالات
|
||||
|
||||
تشمل منطق إدارة الحالة. [RecoilJS](https://recoiljs.org) يتولّى ذلك.
|
||||
تشمل منطق إدارة الحالة. [Jotai](https://jotai.org) يتولّى ذلك.
|
||||
|
||||
* المحددات: انظر [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) لمزيد من التفاصيل.
|
||||
* المحددات: الذرات المشتقة (باستخدام `createAtomSelector`) تحسب قيماً من ذرات أخرى وتخزن نتائجها في الذاكرة مؤقتاً تلقائياً.
|
||||
|
||||
لا تزال إدارة الحالة المدمجة في React تتولّى الحالة داخل المكوّن.
|
||||
|
||||
|
||||
+2
-2
@@ -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) لمزيد من المعلومات حول إدارة الحالة."
|
||||
|
||||
|
||||
+3
-3
@@ -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 للتعامل مع حالة نطاق مفاتيح الاختصار وجعلها متاحة في جميع أنحاء التطبيق.
|
||||
|
||||
+3
-1
@@ -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>
|
||||
|
||||
## ما هي التطبيقات؟
|
||||
@@ -15,6 +15,7 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
|
||||
|
||||
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
|
||||
* أنشئ وظائف منطقية مع مشغلات مخصصة
|
||||
* حدد المهارات لوكلاء الذكاء الاصطناعي
|
||||
* انشر التطبيق نفسه عبر مساحات عمل متعددة
|
||||
|
||||
## المتطلبات الأساسية
|
||||
@@ -31,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
|
||||
```
|
||||
@@ -45,7 +39,7 @@ yarn twenty app:dev
|
||||
يدعم المُنشئ ثلاثة أوضاع للتحكم في ملفات الأمثلة التي سيتم تضمينها:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# الافتراضي (شامل): جميع الأمثلة (كائن، حقل، دالة منطقية، مكوّن الواجهة الأمامية، عرض، عنصر قائمة التنقل)
|
||||
# الافتراضي (شامل): جميع الأمثلة (كائن، حقل، دالة منطقية، مكوّن الواجهة الأمامية، عرض، عنصر قائمة التنقل، مهارة)
|
||||
npx create-twenty-app@latest my-app
|
||||
|
||||
# الأدنى: الملفات الأساسية فقط (application-config.ts و default-role.ts)
|
||||
@@ -58,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
|
||||
```
|
||||
|
||||
@@ -86,7 +83,7 @@ yarn twenty help
|
||||
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
|
||||
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
|
||||
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
|
||||
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالة ما بعد التثبيت) بالإضافة إلى ملفات الأمثلة بحسب وضع الإنشاء
|
||||
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
|
||||
|
||||
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
|
||||
|
||||
@@ -113,16 +110,19 @@ my-twenty-app/
|
||||
│ └── example-field.ts # تعريف حقل مستقل — مثال
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # دالة منطقية — مثال
|
||||
│ ├── pre-install.ts # دالة منطقية لما قبل التثبيت
|
||||
│ └── post-install.ts # دالة منطقية لما بعد التثبيت
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # مكوّن واجهة أمامية — مثال
|
||||
├── views/
|
||||
│ └── example-view.ts # تعريف عرض محفوظ — مثال
|
||||
└── navigation-menu-items/
|
||||
└── example-navigation-menu-item.ts # رابط تنقّل في الشريط الجانبي — مثال
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # رابط تنقّل في الشريط الجانبي — مثال
|
||||
└── skills/
|
||||
└── 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`، تختار ملفات الأمثلة التي تريد تضمينها.
|
||||
|
||||
بشكل عام:
|
||||
|
||||
@@ -139,18 +139,21 @@ my-twenty-app/
|
||||
|
||||
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
|
||||
|
||||
| دالة مساعدة | نوع الكيان |
|
||||
| ---------------------------- | --------------------------------- |
|
||||
| `defineObject()` | تعريفات كائنات مخصصة |
|
||||
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
|
||||
| `defineFrontComponent()` | Front component definitions |
|
||||
| `defineRole()` | تعريفات الأدوار |
|
||||
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
|
||||
| `defineView()` | تعريفات العروض المحفوظة |
|
||||
| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل |
|
||||
| دالة مساعدة | نوع الكيان |
|
||||
| ---------------------------------- | ---------------------------------------------- |
|
||||
| `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>
|
||||
|
||||
مثال على كيان تم اكتشافه:
|
||||
@@ -168,8 +171,8 @@ export default defineObject({
|
||||
|
||||
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
|
||||
|
||||
* `yarn twenty app:dev` سيولّد تلقائيًا عميل API مضبوط الأنواع في `node_modules/twenty-sdk/generated` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
|
||||
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات تحت `src/` لكائناتك المخصصة أو الوظائف أو المكونات الواجهية أو الأدوار.
|
||||
* سيقوم `yarn twenty app:dev` بتوليد عميلين API مضبوطي الأنواع تلقائيًا في `node_modules/twenty-sdk/generated`: `CoreApiClient` (لبيانات مساحة العمل عبر `/graphql`) و`MetadataApiClient` (لتكوين مساحة العمل وتحميل الملفات عبر `/metadata`).
|
||||
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك.
|
||||
|
||||
## المصادقة
|
||||
|
||||
@@ -212,16 +215,19 @@ yarn twenty auth:status
|
||||
|
||||
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
|
||||
|
||||
| دالة | الغرض |
|
||||
| ---------------------------- | ---------------------------------------------------- |
|
||||
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
|
||||
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
|
||||
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
|
||||
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
|
||||
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
|
||||
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
|
||||
| `defineView()` | تعريف العروض المحفوظة للكائنات |
|
||||
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
|
||||
| دالة | الغرض |
|
||||
| ---------------------------------- | ---------------------------------------------------- |
|
||||
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
|
||||
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
|
||||
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
|
||||
| `definePreInstallLogicFunction()` | تعريف دالة منطقية لما قبل التثبيت (واحدة لكل تطبيق) |
|
||||
| `definePostInstallLogicFunction()` | تعريف دالة منطقية لما بعد التثبيت (واحدة لكل تطبيق) |
|
||||
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
|
||||
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
|
||||
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
|
||||
| `defineView()` | تعريف العروض المحفوظة للكائنات |
|
||||
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
|
||||
| `defineSkill()` | عرّف مهارات وكيل الذكاء الاصطناعي |
|
||||
|
||||
تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
|
||||
|
||||
@@ -307,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)
|
||||
@@ -321,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:
|
||||
@@ -329,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,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -354,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).
|
||||
|
||||
#### الأدوار والصلاحيات
|
||||
|
||||
@@ -428,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';
|
||||
@@ -493,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.
|
||||
@@ -501,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,
|
||||
@@ -518,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"
|
||||
@@ -537,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`:
|
||||
@@ -648,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: {
|
||||
@@ -701,7 +731,7 @@ export default defineLogicFunction({
|
||||
* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة أحداث (cron، وأحداث قاعدة البيانات، والمسارات) في الوقت نفسه.
|
||||
|
||||
<Note>
|
||||
**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
|
||||
**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
|
||||
</Note>
|
||||
|
||||
### المكوّنات الأمامية
|
||||
@@ -709,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 = () => {
|
||||
@@ -732,27 +762,67 @@ export default defineFrontComponent({
|
||||
النقاط الرئيسية:
|
||||
|
||||
* المكوّنات الأمامية هي مكوّنات React تُعرَض ضمن سياقات معزولة داخل Twenty.
|
||||
* استخدم لاحقة الملف `*.front-component.tsx` للاكتشاف التلقائي.
|
||||
* يشير الحقل `component` إلى مكوّن React الخاص بك.
|
||||
* يتم بناء المكوّنات ومزامنتها تلقائيًا أثناء `yarn twenty app:dev`.
|
||||
|
||||
يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين:
|
||||
|
||||
* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة مكوّن أمامي جديد.
|
||||
* **يدوي**: أنشئ ملفًا جديدًا `*.front-component.tsx` واستخدم `defineFrontComponent()`.
|
||||
* **يدوي**: أنشئ ملفًا جديدًا `.tsx` واستخدم `defineFrontComponent()` مع اتباع النمط نفسه.
|
||||
|
||||
### عميل مُولَّد مضبوط الأنواع
|
||||
### المهارات
|
||||
|
||||
يُولَّد العميل مضبوط الأنواع تلقائيًا بواسطة `yarn twenty app:dev` ويُخزَّن في `node_modules/twenty-sdk/generated` استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
|
||||
تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج:
|
||||
|
||||
```typescript
|
||||
import Twenty from '~/generated';
|
||||
// src/skills/example-skill.ts
|
||||
import { defineSkill } from 'twenty-sdk';
|
||||
|
||||
const client = new Twenty();
|
||||
const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
export default defineSkill({
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
name: 'sales-outreach',
|
||||
label: 'التواصل البيعي',
|
||||
description: 'يرشد وكيل الذكاء الاصطناعي خلال عملية منظّمة للتواصل البيعي',
|
||||
icon: 'IconBrain',
|
||||
content: `أنت مساعد للتواصل البيعي. عند التواصل مع عميل محتمل:
|
||||
1. ابحث عن الشركة وآخر الأخبار
|
||||
2. حدِّد دور العميل المحتمل ونقاط الألم المرجّحة
|
||||
3. صِغ رسالة مخصّصة تشير إلى تفاصيل محدّدة
|
||||
4. حافظ على نبرة احترافية ولكن حوارية`,
|
||||
});
|
||||
```
|
||||
|
||||
يُعاد توليد العميل تلقائيًا بواسطة `yarn twenty app:dev` كلما تغيّرت كائناتك أو حقولك.
|
||||
النقاط الرئيسية:
|
||||
|
||||
* `name` هي سلسلة معرّف فريدة للمهارة (يُنصَح باستخدام kebab-case).
|
||||
* `label` هو اسم العرض المقروء للبشر الظاهر في واجهة المستخدم.
|
||||
* `content` يحتوي على تعليمات المهارة — وهو النص الذي يستخدمه وكيل الذكاء الاصطناعي.
|
||||
* `icon` (اختياري) يحدّد الأيقونة المعروضة في واجهة المستخدم.
|
||||
* `description` (اختياري) يوفّر سياقًا إضافيًا حول غرض المهارة.
|
||||
|
||||
يمكنك إنشاء مهارات جديدة بطريقتين:
|
||||
|
||||
* **مُنشأ بالقالب**: شغِّل `yarn twenty entity:add` واختر خيار إضافة مهارة جديدة.
|
||||
* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineSkill()` مع اتباع النمط نفسه.
|
||||
|
||||
### عملاء مُولَّدون مضبوطو الأنواع
|
||||
|
||||
يتم توليد عميلين مضبوطي الأنواع تلقائيًا بواسطة `yarn twenty app:dev` وتخزينهما في `node_modules/twenty-sdk/generated` استنادًا إلى مخطط مساحة العمل لديك:
|
||||
|
||||
* **`CoreApiClient`** — يُجري استعلامات إلى نقطة النهاية `/graphql` للحصول على بيانات مساحة العمل
|
||||
* **`MetadataApiClient`** — يستعلم عن نقطة النهاية `/metadata` لتكوين مساحة العمل وتحميل الملفات.
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
|
||||
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` كلما تغيّرت كائناتك أو حقولك.
|
||||
|
||||
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
|
||||
|
||||
@@ -767,6 +837,53 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application-config.ts` عبر `defaultRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف المنطقية في تطبيقك.
|
||||
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `defaultRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
|
||||
|
||||
#### رفع الملفات
|
||||
|
||||
يتضمن `MetadataApiClient` المُولَّد طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const metadataClient = new MetadataApiClient();
|
||||
|
||||
const fileBuffer = fs.readFileSync('./invoice.pdf');
|
||||
|
||||
const uploadedFile = await metadataClient.uploadFile(
|
||||
fileBuffer, // file contents as a Buffer
|
||||
'invoice.pdf', // filename
|
||||
'application/pdf', // MIME type (defaults to 'application/octet-stream')
|
||||
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier
|
||||
);
|
||||
|
||||
console.log(uploadedFile);
|
||||
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
||||
```
|
||||
|
||||
توقيع الطريقة:
|
||||
|
||||
```typescript
|
||||
uploadFile(
|
||||
fileBuffer: Buffer,
|
||||
filename: string,
|
||||
contentType: string,
|
||||
fieldMetadataUniversalIdentifier: string,
|
||||
): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }>
|
||||
```
|
||||
|
||||
| المعلمة | النوع | الوصف |
|
||||
| ---------------------------------- | -------- | ---------------------------------------------------------------------------------- |
|
||||
| `fileBuffer` | `Buffer` | المحتوى الخام للملف |
|
||||
| `filename` | `string` | اسم الملف (يُستخدم للتخزين والعرض) |
|
||||
| `contentType` | `string` | نوع MIME للملف (القيمة الافتراضية هي `application/octet-stream` إذا لم يتم تحديده) |
|
||||
| `fieldMetadataUniversalIdentifier` | `string` | قيمة `universalIdentifier` لحقل نوع الملف في كائنك |
|
||||
|
||||
النقاط الرئيسية:
|
||||
|
||||
* تتوفر طريقة `uploadFile` على `MetadataApiClient` لأن عملية الـ mutation الخاصة بالرفع تُعالَج عبر نقطة النهاية `/metadata`.
|
||||
* تستخدم `universalIdentifier` الخاص بالحقل (وليس المعرّف الخاص بمساحة العمل)، ليعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك — بما يتماشى مع كيفية إشارة التطبيقات إلى الحقول في كل مكان آخر.
|
||||
* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع.
|
||||
|
||||
### مثال Hello World
|
||||
|
||||
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف المنطقية والمكوّنات الأمامية ومشغّلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
|
||||
|
||||
@@ -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 # افتراضي
|
||||

|
||||
|
||||
<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>
|
||||
|
||||
+1
-1
@@ -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):
|
||||
|
||||
* تحديثات فورية لمعظم المقاييس
|
||||
* استخدم زر التحديث لإجراء تحديث يدوي عند الحاجة
|
||||
* تُحفَظ البيانات التاريخية لتحليل الاتجاهات
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user