Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -15,7 +15,6 @@
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
|
||||
18
packages/twenty-docker/build/docs/Dockerfile
Normal file
18
packages/twenty-docker/build/docs/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
FROM node:18.16.0-alpine as docs
|
||||
|
||||
WORKDIR /app/docs
|
||||
|
||||
ARG SHOULD_INDEX_DOC
|
||||
|
||||
COPY ./docs/package.json .
|
||||
COPY ./docs/yarn.lock .
|
||||
RUN yarn install
|
||||
|
||||
COPY ./docs .
|
||||
RUN npm run build
|
||||
|
||||
RUN yarn global add serve
|
||||
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
||||
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the documentation."
|
||||
|
||||
CMD ["serve", "-s", "./build"]
|
||||
29
packages/twenty-docker/build/front/Dockerfile
Normal file
29
packages/twenty-docker/build/front/Dockerfile
Normal file
@ -0,0 +1,29 @@
|
||||
FROM node:18.16.0-alpine as build
|
||||
|
||||
ARG REACT_APP_SERVER_BASE_URL
|
||||
ARG REACT_APP_SERVER_AUTH_URL
|
||||
ARG REACT_APP_SERVER_FILES_URL
|
||||
|
||||
COPY ./packages/ /app/packages
|
||||
|
||||
WORKDIR /app/front
|
||||
COPY ./front .
|
||||
|
||||
RUN yarn install
|
||||
RUN yarn build
|
||||
|
||||
COPY ./infra/build/front/serve.json ./build
|
||||
|
||||
FROM node:18.16.0-alpine as front
|
||||
|
||||
WORKDIR /app/front
|
||||
|
||||
COPY --from=build /app/front/build ./build
|
||||
COPY ./front/scripts/inject-runtime-env.sh /app/front/scripts/inject-runtime-env.sh
|
||||
|
||||
RUN yarn global add serve
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
||||
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the frontend."
|
||||
|
||||
CMD ["/bin/sh", "-c", "/app/front/scripts/inject-runtime-env.sh && serve build"]
|
||||
6
packages/twenty-docker/build/front/serve.json
Normal file
6
packages/twenty-docker/build/front/serve.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{ "source": "!static/**", "destination": "/index.html" }
|
||||
],
|
||||
"directoryListing": false
|
||||
}
|
||||
45
packages/twenty-docker/build/postgres/Dockerfile
Normal file
45
packages/twenty-docker/build/postgres/Dockerfile
Normal file
@ -0,0 +1,45 @@
|
||||
ARG IMAGE_TAG='15.5.0-debian-11-r15'
|
||||
|
||||
FROM bitnami/postgresql:${IMAGE_TAG}
|
||||
|
||||
ARG PG_MAIN_VERSION=15
|
||||
ARG PG_GRAPHQL_VERSION=1.4.2
|
||||
ARG WRAPPERS_VERSION=0.2.0
|
||||
ARG TARGETARCH
|
||||
|
||||
USER root
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
TARGETARCH='arm64'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
TARGETARCH='amd64'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac;
|
||||
|
||||
RUN apt update && apt install curl -y
|
||||
|
||||
# Install precompiled pg_graphql extensions
|
||||
RUN curl -L "https://github.com/supabase/pg_graphql/releases/download/v${PG_GRAPHQL_VERSION}/pg_graphql-v${PG_GRAPHQL_VERSION}-pg${PG_MAIN_VERSION}-${TARGETARCH}-linux-gnu.deb" -o pg_graphql.deb
|
||||
RUN dpkg --install pg_graphql.deb
|
||||
RUN cp /usr/share/postgresql/${PG_MAIN_VERSION}/extension/pg_graphql* /opt/bitnami/postgresql/share/extension/
|
||||
RUN cp /usr/lib/postgresql/${PG_MAIN_VERSION}/lib/pg_graphql* /opt/bitnami/postgresql/lib/
|
||||
|
||||
# Install precompiled supabase wrappers extensions
|
||||
RUN curl -L "https://github.com/supabase/wrappers/releases/download/v${WRAPPERS_VERSION}/wrappers-v${WRAPPERS_VERSION}-pg${PG_MAIN_VERSION}-${TARGETARCH}-linux-gnu.deb" -o wrappers.deb
|
||||
RUN dpkg --install wrappers.deb
|
||||
RUN cp /usr/share/postgresql/${PG_MAIN_VERSION}/extension/wrappers* /opt/bitnami/postgresql/share/extension/
|
||||
RUN cp /usr/lib/postgresql/${PG_MAIN_VERSION}/lib/wrappers* /opt/bitnami/postgresql/lib/
|
||||
|
||||
COPY ./infra/build/postgres/init.sql /docker-entrypoint-initdb.d/
|
||||
|
||||
USER 1001
|
||||
ENTRYPOINT [ "/opt/bitnami/scripts/postgresql/entrypoint.sh" ]
|
||||
CMD [ "/opt/bitnami/scripts/postgresql/run.sh" ]
|
||||
10
packages/twenty-docker/build/postgres/init.sql
Normal file
10
packages/twenty-docker/build/postgres/init.sql
Normal file
@ -0,0 +1,10 @@
|
||||
SELECT 'CREATE DATABASE "default"'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'default')\gexec
|
||||
|
||||
SELECT 'CREATE DATABASE "test"'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'test')\gexec
|
||||
|
||||
SELECT 'CREATE USER twenty PASSWORD ''twenty'''
|
||||
WHERE NOT EXISTS (SELECT FROM pg_user WHERE usename = 'twenty')\gexec
|
||||
|
||||
SELECT 'ALTER ROLE twenty superuser'\gexec
|
||||
15
packages/twenty-docker/build/server/Dockerfile
Normal file
15
packages/twenty-docker/build/server/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM node:18.16.0-alpine as build
|
||||
|
||||
WORKDIR /app/server
|
||||
COPY ./server/package.json ./
|
||||
COPY ./server/yarn.lock ./
|
||||
COPY ./server/patches ./patches
|
||||
RUN yarn install
|
||||
|
||||
COPY ./server .
|
||||
RUN yarn build
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
|
||||
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the backend, ensuring it deploys faster and runs the same way regardless of the deployment environment."
|
||||
|
||||
CMD ["node", "dist/src/main"]
|
||||
69
packages/twenty-docker/dev/Makefile
Normal file
69
packages/twenty-docker/dev/Makefile
Normal file
@ -0,0 +1,69 @@
|
||||
build:
|
||||
@docker compose down
|
||||
rm -rf ../../front/node_modules
|
||||
rm -rf ../../server/node_modules
|
||||
rm -rf ../../docs/node_modules
|
||||
@docker volume rm twenty_node_modules_front > /dev/null 2>&1 || true
|
||||
@docker volume rm twenty_node_modules_server > /dev/null 2>&1 || true
|
||||
@docker volume rm twenty_node_modules_docs > /dev/null 2>&1 || true
|
||||
@docker compose build
|
||||
|
||||
provision-postgres-docker:
|
||||
@docker stop twenty_postgres || true
|
||||
@docker rm twenty_postgres || true
|
||||
@docker volume rm twenty_db_data || true
|
||||
@docker compose up --build postgres -d
|
||||
|
||||
provision-postgres-macos-arm:
|
||||
sh ./scripts/setup-postgres-macos-arm.sh
|
||||
|
||||
provision-postgres-macos-intel:
|
||||
sh ./scripts/setup-postgres-macos-intel.sh
|
||||
|
||||
provision-postgres-linux:
|
||||
sh ./scripts/setup-postgres-linux.sh
|
||||
|
||||
up:
|
||||
@docker compose up -d
|
||||
|
||||
down:
|
||||
@docker compose down -v
|
||||
|
||||
sh:
|
||||
@docker compose exec twenty-dev sh
|
||||
|
||||
front-start:
|
||||
@docker compose exec twenty-dev sh -c "cd ./front && yarn start"
|
||||
|
||||
front-lint:
|
||||
@docker compose exec twenty-dev sh -c "cd ./front && yarn lint"
|
||||
|
||||
front-test:
|
||||
@docker compose exec twenty-dev sh -c "cd ./front && yarn test"
|
||||
|
||||
front-coverage:
|
||||
@docker compose exec twenty-dev sh -c "cd ./front && yarn coverage"
|
||||
|
||||
front-graphql-generate:
|
||||
@docker compose exec twenty-dev sh -c "cd ./front && yarn graphql:generate"
|
||||
|
||||
front-storybook:
|
||||
@docker compose exec twenty-dev sh -c "cd ./front && yarn storybook:dev"
|
||||
|
||||
server-start:
|
||||
@docker compose exec twenty-dev sh -c "cd /app/server && yarn start:dev"
|
||||
|
||||
server-database-init:
|
||||
@docker compose exec twenty-dev sh -c "cd /app/server && yarn database:init"
|
||||
|
||||
server-database-setup:
|
||||
@docker compose exec twenty-dev sh -c "cd /app/server && yarn database:setup"
|
||||
|
||||
server-database-migrate:
|
||||
@docker compose exec twenty-dev sh -c "cd /app/server && yarn database:migrate"
|
||||
|
||||
server-database-seed:
|
||||
@docker compose exec twenty-dev sh -c "cd /app/server && yarn database:seed"
|
||||
|
||||
server-database-reset:
|
||||
@docker compose exec twenty-dev sh -c "cd /app/server && yarn database:reset"
|
||||
42
packages/twenty-docker/dev/docker-compose.yml
Normal file
42
packages/twenty-docker/dev/docker-compose.yml
Normal file
@ -0,0 +1,42 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
twenty-dev:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: ./infra/dev/twenty-dev/Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3001:3001"
|
||||
- "6006:6006"
|
||||
volumes:
|
||||
- ../..:/app
|
||||
- /app/front/node_modules
|
||||
- /app/server/node_modules
|
||||
depends_on:
|
||||
- postgres
|
||||
twenty-docs:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: ./infra/dev/twenty-docs/Dockerfile
|
||||
ports:
|
||||
- "5001:5001"
|
||||
volumes:
|
||||
- ../../docs:/app/docs
|
||||
- twenty_node_modules_docs:/app/docs/node_modules
|
||||
postgres:
|
||||
container_name: twenty_postgres
|
||||
build: ./postgres
|
||||
volumes:
|
||||
- twenty_db_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=twenty
|
||||
- POSTGRES_PASSWORD=twenty
|
||||
- POSTGRES_DB=default
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
twenty_db_data:
|
||||
name: twenty_db_data
|
||||
twenty_node_modules_front:
|
||||
twenty_node_modules_server:
|
||||
twenty_node_modules_docs:
|
||||
35
packages/twenty-docker/dev/postgres/Dockerfile
Normal file
35
packages/twenty-docker/dev/postgres/Dockerfile
Normal file
@ -0,0 +1,35 @@
|
||||
ARG PG_MAIN_VERSION=15.5
|
||||
|
||||
FROM postgres:${PG_MAIN_VERSION}-bullseye as postgres
|
||||
|
||||
ARG PG_MAIN_VERSION=15
|
||||
ARG PG_GRAPHQL_VERSION=1.4.2
|
||||
ARG WRAPPERS_VERSION=0.2.0
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
TARGETARCH='arm64'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
TARGETARCH='amd64'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac;
|
||||
|
||||
RUN apt update && apt install curl -y
|
||||
|
||||
# Install precompiled pg_graphql extensions
|
||||
RUN curl -L "https://github.com/supabase/pg_graphql/releases/download/v${PG_GRAPHQL_VERSION}/pg_graphql-v${PG_GRAPHQL_VERSION}-pg${PG_MAIN_VERSION}-${TARGETARCH}-linux-gnu.deb" -o pg_graphql.deb
|
||||
RUN dpkg --install pg_graphql.deb
|
||||
|
||||
# Install precompiled supabase wrappers extensions
|
||||
RUN curl -L "https://github.com/supabase/wrappers/releases/download/v${WRAPPERS_VERSION}/wrappers-v${WRAPPERS_VERSION}-pg${PG_MAIN_VERSION}-${TARGETARCH}-linux-gnu.deb" -o wrappers.deb
|
||||
RUN dpkg --install wrappers.deb
|
||||
|
||||
COPY init.sql /docker-entrypoint-initdb.d/
|
||||
10
packages/twenty-docker/dev/postgres/init.sql
Normal file
10
packages/twenty-docker/dev/postgres/init.sql
Normal file
@ -0,0 +1,10 @@
|
||||
SELECT 'CREATE DATABASE "default"'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'default')\gexec
|
||||
|
||||
SELECT 'CREATE DATABASE "test"'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'test')\gexec
|
||||
|
||||
SELECT 'CREATE USER twenty PASSWORD ''twenty'''
|
||||
WHERE NOT EXISTS (SELECT FROM pg_user WHERE usename = 'twenty')\gexec
|
||||
|
||||
SELECT 'ALTER ROLE twenty superuser'\gexec
|
||||
79
packages/twenty-docker/dev/scripts/setup-postgres-linux.sh
Executable file
79
packages/twenty-docker/dev/scripts/setup-postgres-linux.sh
Executable file
@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colors
|
||||
RED=31
|
||||
GREEN=32
|
||||
BLUE=34
|
||||
|
||||
# Function to display colored output
|
||||
function echo_header {
|
||||
COLOR=$1
|
||||
MESSAGE=$2
|
||||
echo -e "\e[${COLOR}m\n=======================================================\e[0m"
|
||||
echo -e "\e[${COLOR}m${MESSAGE}\e[0m"
|
||||
echo -e "\e[${COLOR}m=======================================================\e[0m"
|
||||
}
|
||||
|
||||
# Function to handle errors
|
||||
function handle_error {
|
||||
echo_header $RED "Error: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cat << "EOF"
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@#*+=================@@@@@%*+=========++*%@@@@@@@
|
||||
@@@@#- .+@@%=. .+@@@@@
|
||||
@@@- .*@@%- .#@@@
|
||||
@@= .=+++++++++++*#@@@= -++++++++++- %@@
|
||||
@@. %@@@@@@@@@@@@@@@+ =%@@@@@@@@@@@@= +@@
|
||||
@@. .@@@@@@@@@@@@@@+. -%@@@@@@@@@@@@@@+ +@@
|
||||
@@. .@@@@@@@@@@@@*. -#@@#:=@@@@@@@@@@@= +@@
|
||||
@@ @@@@@@@@@@#: :#@@#: -@@@@@@@@@@@= +@@
|
||||
@@#====#@@@@@@@@#- .*@@@= -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@@@@%- .*@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@@%= +@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@+ =@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@+. -%@@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@*. -%@@@@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@#: :#@@@@@@@@@@@@@# -@@@@@@@@@@@+ +@@
|
||||
@@@#: :#@@@@@@@@@@@@@@@# :@@@@@@@@@@@= +@@
|
||||
@@= :+*+++++++++++*%@@@. :+++++++++- %@@
|
||||
@@ :@@@%. .#@@@
|
||||
@@- :@@@@@+: .+@@@@@
|
||||
@@@#+===================+%@@@@@@@%*++=======++*%@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
EOF
|
||||
|
||||
echo_header $BLUE " DATABASE SETUP"
|
||||
|
||||
PG_MAIN_VERSION=15
|
||||
PG_GRAPHQL_VERSION=1.3.0
|
||||
TARGETARCH=$(dpkg --print-architecture)
|
||||
|
||||
# Install PostgresSQL
|
||||
echo_header $GREEN "Step [1/4]: Installing PostgreSQL..."
|
||||
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||
wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc &>/dev/null
|
||||
sudo apt update -y || handle_error "Failed to update package list."
|
||||
sudo apt install -y postgresql-$PG_MAIN_VERSION postgresql-contrib || handle_error "Failed to install PostgreSQL."su
|
||||
sudo apt install -y curl || handle_error "Failed to install curl."
|
||||
|
||||
# Install pg_graphql extensions
|
||||
echo_header $GREEN "Step [2/4]: Installing GraphQL for PostgreSQL..."
|
||||
curl -L https://github.com/supabase/pg_graphql/releases/download/v$PG_GRAPHQL_VERSION/pg_graphql-v$PG_GRAPHQL_VERSION-pg$PG_MAIN_VERSION-$TARGETARCH-linux-gnu.deb -o pg_graphql.deb || handle_error "Failed to download pg_graphql package."
|
||||
sudo dpkg --install pg_graphql.deb || handle_error "Failed to install pg_graphql package."
|
||||
rm pg_graphql.deb
|
||||
|
||||
# Start postgresql service
|
||||
echo_header $GREEN "Step [3/4]: Starting PostgreSQL service..."
|
||||
if sudo service postgresql start; then
|
||||
echo "PostgreSQL service started successfully."
|
||||
else
|
||||
handle_error "Failed to start PostgreSQL service."
|
||||
fi
|
||||
|
||||
# Run the init.sql to setup database
|
||||
echo_header $GREEN "Step [4/4]: Setting up database..."
|
||||
cp ./postgres/init.sql /tmp/init.sql
|
||||
sudo -u postgres psql -f /tmp/init.sql || handle_error "Failed to execute init.sql script."
|
||||
108
packages/twenty-docker/dev/scripts/setup-postgres-macos-arm.sh
Executable file
108
packages/twenty-docker/dev/scripts/setup-postgres-macos-arm.sh
Executable file
@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colors
|
||||
RED=31
|
||||
GREEN=32
|
||||
BLUE=34
|
||||
|
||||
# Function to display colored output
|
||||
function echo_header {
|
||||
COLOR=$1
|
||||
MESSAGE=$2
|
||||
echo -e "\e[${COLOR}m\n=======================================================\e[0m"
|
||||
echo -e "\e[${COLOR}m${MESSAGE}\e[0m"
|
||||
echo -e "\e[${COLOR}m=======================================================\e[0m"
|
||||
}
|
||||
|
||||
# Function to handle errors
|
||||
function handle_error {
|
||||
echo_header $RED "Error: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cat << "EOF"
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@#*+=================@@@@@%*+=========++*%@@@@@@@
|
||||
@@@@#- .+@@%=. .+@@@@@
|
||||
@@@- .*@@%- .#@@@
|
||||
@@= .=+++++++++++*#@@@= -++++++++++- %@@
|
||||
@@. %@@@@@@@@@@@@@@@+ =%@@@@@@@@@@@@= +@@
|
||||
@@. .@@@@@@@@@@@@@@+. -%@@@@@@@@@@@@@@+ +@@
|
||||
@@. .@@@@@@@@@@@@*. -#@@#:=@@@@@@@@@@@= +@@
|
||||
@@ @@@@@@@@@@#: :#@@#: -@@@@@@@@@@@= +@@
|
||||
@@#====#@@@@@@@@#- .*@@@= -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@@@@%- .*@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@@%= +@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@+ =@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@+. -%@@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@*. -%@@@@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@#: :#@@@@@@@@@@@@@# -@@@@@@@@@@@+ +@@
|
||||
@@@#: :#@@@@@@@@@@@@@@@# :@@@@@@@@@@@= +@@
|
||||
@@= :+*+++++++++++*%@@@. :+++++++++- %@@
|
||||
@@ :@@@%. .#@@@
|
||||
@@- :@@@@@+: .+@@@@@
|
||||
@@@#+===================+%@@@@@@@%*++=======++*%@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
EOF
|
||||
|
||||
echo_header $BLUE " DATABASE SETUP"
|
||||
|
||||
PG_MAIN_VERSION=15
|
||||
PG_GRAPHQL_VERSION=1.3.0
|
||||
CARGO_PGRX_VERSION=0.9.8
|
||||
|
||||
current_directory=$(pwd)
|
||||
|
||||
# Install PostgresSQL
|
||||
echo_header $GREEN "Step [1/4]: Installing PostgreSQL..."
|
||||
|
||||
brew reinstall postgresql@$PG_MAIN_VERSION
|
||||
|
||||
# Install pg_graphql extensions
|
||||
echo_header $GREEN "Step [2/4]: Installing GraphQL for PostgreSQL..."
|
||||
|
||||
# Uninstall existing Rust installation if found
|
||||
existing_rust_path=$(which rustc)
|
||||
if [ -n "$existing_rust_path" ]; then
|
||||
echo "Uninstalling existing Rust installation..."
|
||||
rm -rf "$existing_rust_path"
|
||||
fi
|
||||
|
||||
# To force a reinstall of cargo-pgrx, pass --force to the command below
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
source "$HOME/.cargo/env"
|
||||
cargo install --locked cargo-pgrx@$CARGO_PGRX_VERSION --force
|
||||
cargo pgrx init --pg$PG_MAIN_VERSION download
|
||||
|
||||
# Create a temporary directory
|
||||
temp_dir=$(mktemp -d)
|
||||
cd "$temp_dir"
|
||||
|
||||
curl -LJO https://github.com/supabase/pg_graphql/archive/refs/tags/v$PG_GRAPHQL_VERSION.zip || handle_error "Failed to download pg_graphql package."
|
||||
|
||||
unzip pg_graphql-$PG_GRAPHQL_VERSION.zip
|
||||
|
||||
[[ ":$PATH:" != *":/opt/homebrew/opt/postgresql@$PG_MAIN_VERSION/bin:"* ]] && PATH="/opt/homebrew/opt/postgresql@$PG_MAIN_VERSION/bin:${PATH}"
|
||||
|
||||
cd "pg_graphql-$PG_GRAPHQL_VERSION"
|
||||
cargo pgrx install --release --pg-config /opt/homebrew/opt/postgresql@$PG_MAIN_VERSION/bin/pg_config
|
||||
|
||||
# # Clean up the temporary directory
|
||||
echo "Cleaning up..."
|
||||
cd "$current_directory"
|
||||
rm -rf "$temp_dir"
|
||||
|
||||
# Start postgresql service
|
||||
echo_header $GREEN "Step [3/4]: Starting PostgreSQL service..."
|
||||
|
||||
|
||||
if brew services start postgresql@$PG_MAIN_VERSION; then
|
||||
echo "PostgreSQL service started successfully."
|
||||
else
|
||||
handle_error "Failed to start PostgreSQL service."
|
||||
fi
|
||||
|
||||
# Run the init.sql to setup database
|
||||
echo_header $GREEN "Step [4/4]: Setting up database..."
|
||||
cp ./postgres/init.sql /tmp/init.sql
|
||||
psql -f /tmp/init.sql -d postgres|| handle_error "Failed to execute init.sql script."
|
||||
108
packages/twenty-docker/dev/scripts/setup-postgres-macos-intel.sh
Executable file
108
packages/twenty-docker/dev/scripts/setup-postgres-macos-intel.sh
Executable file
@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colors
|
||||
RED=31
|
||||
GREEN=32
|
||||
BLUE=34
|
||||
|
||||
# Function to display colored output
|
||||
function echo_header {
|
||||
COLOR=$1
|
||||
MESSAGE=$2
|
||||
echo -e "\e[${COLOR}m\n=======================================================\e[0m"
|
||||
echo -e "\e[${COLOR}m${MESSAGE}\e[0m"
|
||||
echo -e "\e[${COLOR}m=======================================================\e[0m"
|
||||
}
|
||||
|
||||
# Function to handle errors
|
||||
function handle_error {
|
||||
echo_header $RED "Error: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cat << "EOF"
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@#*+=================@@@@@%*+=========++*%@@@@@@@
|
||||
@@@@#- .+@@%=. .+@@@@@
|
||||
@@@- .*@@%- .#@@@
|
||||
@@= .=+++++++++++*#@@@= -++++++++++- %@@
|
||||
@@. %@@@@@@@@@@@@@@@+ =%@@@@@@@@@@@@= +@@
|
||||
@@. .@@@@@@@@@@@@@@+. -%@@@@@@@@@@@@@@+ +@@
|
||||
@@. .@@@@@@@@@@@@*. -#@@#:=@@@@@@@@@@@= +@@
|
||||
@@ @@@@@@@@@@#: :#@@#: -@@@@@@@@@@@= +@@
|
||||
@@#====#@@@@@@@@#- .*@@@= -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@@@@%- .*@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@@%= +@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@@@+ =@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@@@+. -%@@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@@@*. -%@@@@@@@@@@@# -@@@@@@@@@@@= +@@
|
||||
@@@@@#: :#@@@@@@@@@@@@@# -@@@@@@@@@@@+ +@@
|
||||
@@@#: :#@@@@@@@@@@@@@@@# :@@@@@@@@@@@= +@@
|
||||
@@= :+*+++++++++++*%@@@. :+++++++++- %@@
|
||||
@@ :@@@%. .#@@@
|
||||
@@- :@@@@@+: .+@@@@@
|
||||
@@@#+===================+%@@@@@@@%*++=======++*%@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
EOF
|
||||
|
||||
echo_header $BLUE " DATABASE SETUP"
|
||||
|
||||
PG_MAIN_VERSION=15
|
||||
PG_GRAPHQL_VERSION=1.3.0
|
||||
CARGO_PGRX_VERSION=0.9.8
|
||||
|
||||
current_directory=$(pwd)
|
||||
|
||||
# Install PostgresSQL
|
||||
echo_header $GREEN "Step [1/4]: Installing PostgreSQL..."
|
||||
|
||||
brew reinstall postgresql@$PG_MAIN_VERSION
|
||||
|
||||
# Install pg_graphql extensions
|
||||
echo_header $GREEN "Step [2/4]: Installing GraphQL for PostgreSQL..."
|
||||
|
||||
# Uninstall existing Rust installation if found
|
||||
existing_rust_path=$(which rustc)
|
||||
if [ -n "$existing_rust_path" ]; then
|
||||
echo "Uninstalling existing Rust installation..."
|
||||
rm -rf "$existing_rust_path"
|
||||
fi
|
||||
|
||||
# To force a reinstall of cargo-pgrx, pass --force to the command below
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
source "$HOME/.cargo/env"
|
||||
cargo install --locked cargo-pgrx@$CARGO_PGRX_VERSION --force
|
||||
cargo pgrx init --pg$PG_MAIN_VERSION download
|
||||
|
||||
# Create a temporary directory
|
||||
temp_dir=$(mktemp -d)
|
||||
cd "$temp_dir"
|
||||
|
||||
curl -LJO https://github.com/supabase/pg_graphql/archive/refs/tags/v$PG_GRAPHQL_VERSION.zip || handle_error "Failed to download pg_graphql package."
|
||||
|
||||
unzip pg_graphql-$PG_GRAPHQL_VERSION.zip
|
||||
|
||||
[[ ":$PATH:" != *":/usr/local/opt/postgresql@$PG_MAIN_VERSION/bin:"* ]] && PATH="/usr/local/opt/postgresql@$PG_MAIN_VERSION/bin:${PATH}"
|
||||
|
||||
cd "pg_graphql-$PG_GRAPHQL_VERSION"
|
||||
cargo pgrx install --release --pg-config /usr/local/opt/postgresql@$PG_MAIN_VERSION/bin/pg_config
|
||||
|
||||
# # Clean up the temporary directory
|
||||
echo "Cleaning up..."
|
||||
cd "$current_directory"
|
||||
rm -rf "$temp_dir"
|
||||
|
||||
# Start postgresql service
|
||||
echo_header $GREEN "Step [3/4]: Starting PostgreSQL service..."
|
||||
|
||||
|
||||
if brew services start postgresql@$PG_MAIN_VERSION; then
|
||||
echo "PostgreSQL service started successfully."
|
||||
else
|
||||
handle_error "Failed to start PostgreSQL service."
|
||||
fi
|
||||
|
||||
# Run the init.sql to setup database
|
||||
echo_header $GREEN "Step [4/4]: Setting up database..."
|
||||
cp ./postgres/init.sql /tmp/init.sql
|
||||
psql -f /tmp/init.sql -d postgres|| handle_error "Failed to execute init.sql script."
|
||||
21
packages/twenty-docker/dev/twenty-dev/Dockerfile
Normal file
21
packages/twenty-docker/dev/twenty-dev/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
FROM node:18.16-bullseye as twenty-dev
|
||||
|
||||
COPY /../../packages /app/packages
|
||||
|
||||
WORKDIR /app/front
|
||||
|
||||
COPY ../../front/package.json .
|
||||
COPY ../../front/yarn.lock .
|
||||
RUN yarn install
|
||||
RUN npx playwright install-deps
|
||||
|
||||
WORKDIR /app/server
|
||||
|
||||
COPY ../../server/package.json .
|
||||
COPY ../../server/yarn.lock .
|
||||
COPY ../../server/patches ./patches
|
||||
RUN yarn install
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["tail", "-f", "/dev/null"]
|
||||
11
packages/twenty-docker/dev/twenty-docs/Dockerfile
Normal file
11
packages/twenty-docker/dev/twenty-docs/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM node:18.16-bullseye as docs
|
||||
|
||||
WORKDIR /app/docs
|
||||
|
||||
COPY ../../docs/package.json .
|
||||
COPY ../../docs/yarn.lock .
|
||||
RUN yarn
|
||||
|
||||
COPY ../../docs .
|
||||
|
||||
CMD ["yarn", "start"]
|
||||
3
packages/twenty-docker/prod/front/Dockerfile
Normal file
3
packages/twenty-docker/prod/front/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
||||
FROM --platform=linux/amd64 twentycrm/twenty-front as front
|
||||
|
||||
CMD ["/bin/sh", "-c", "/app/front/scripts/inject-runtime-env.sh && serve build"]
|
||||
30
packages/twenty-docker/prod/postgres/Dockerfile
Normal file
30
packages/twenty-docker/prod/postgres/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
ARG PG_MAIN_VERSION=14
|
||||
|
||||
FROM postgres:${PG_MAIN_VERSION} as postgres
|
||||
|
||||
ARG PG_MAIN_VERSION
|
||||
ARG PG_GRAPHQL_VERSION=1.3.0
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
TARGETARCH='arm64'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
TARGETARCH='amd64'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
exit 1; \
|
||||
;; \
|
||||
esac;
|
||||
|
||||
RUN apt update && apt install -y curl
|
||||
|
||||
# Install precompiled pg_graphql extensions
|
||||
RUN curl -L "https://github.com/supabase/pg_graphql/releases/download/v${PG_GRAPHQL_VERSION}/pg_graphql-v${PG_GRAPHQL_VERSION}-pg${PG_MAIN_VERSION}-${TARGETARCH}-linux-gnu.deb" -o pg_graphql.deb
|
||||
RUN dpkg --install pg_graphql.deb
|
||||
|
||||
COPY ./infra/prod/postgres/init.sql /docker-entrypoint-initdb.d/
|
||||
10
packages/twenty-docker/prod/postgres/init.sql
Normal file
10
packages/twenty-docker/prod/postgres/init.sql
Normal file
@ -0,0 +1,10 @@
|
||||
SELECT 'CREATE DATABASE "default"'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'default')\gexec
|
||||
|
||||
SELECT 'CREATE DATABASE "test"'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'test')\gexec
|
||||
|
||||
SELECT 'CREATE USER twenty PASSWORD ''twenty'''
|
||||
WHERE NOT EXISTS (SELECT FROM pg_user WHERE usename = 'twenty')\gexec
|
||||
|
||||
SELECT 'ALTER ROLE twenty superuser'\gexec
|
||||
3
packages/twenty-docker/prod/server/Dockerfile
Normal file
3
packages/twenty-docker/prod/server/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
||||
FROM --platform=linux/amd64 twentycrm/twenty-server as server
|
||||
|
||||
CMD ["node", "dist/src/main"]
|
||||
6
packages/twenty-docker/release/build-front.sh
Executable file
6
packages/twenty-docker/release/build-front.sh
Executable file
@ -0,0 +1,6 @@
|
||||
|
||||
docker buildx build \
|
||||
--push \
|
||||
--no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-f ./infra/build/front/Dockerfile -t twentycrm/twenty-front:0.1.5 -t twentycrm/twenty-front:latest .
|
||||
6
packages/twenty-docker/release/build-postgres.sh
Executable file
6
packages/twenty-docker/release/build-postgres.sh
Executable file
@ -0,0 +1,6 @@
|
||||
|
||||
docker buildx build \
|
||||
--push \
|
||||
--no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-f ./infra/build/postgres/Dockerfile -t twentycrm/twenty-postgres:0.2.0 -t twentycrm/twenty-postgres:latest .
|
||||
6
packages/twenty-docker/release/build-server.sh
Executable file
6
packages/twenty-docker/release/build-server.sh
Executable file
@ -0,0 +1,6 @@
|
||||
|
||||
docker buildx build \
|
||||
--push \
|
||||
--no-cache \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-f ./infra/build/server/Dockerfile -t twentycrm/twenty-server:0.1.5 -t twentycrm/twenty-server:latest .
|
||||
22
packages/twenty-docs/.gitignore
vendored
Normal file
22
packages/twenty-docs/.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
package-lock.json
|
||||
17
packages/twenty-docs/.vale.ini
Normal file
17
packages/twenty-docs/.vale.ini
Normal file
@ -0,0 +1,17 @@
|
||||
StylesPath = ../.github/vale-styles
|
||||
|
||||
MinAlertLevel = suggestion
|
||||
|
||||
Packages = write-good
|
||||
|
||||
[*.{md,mdx}]
|
||||
BasedOnStyles = Vale, write-good, docs
|
||||
Vale.Spelling=warning
|
||||
write-good.E-Prime=No
|
||||
write-good.So=No
|
||||
write-good.ThereIs=No
|
||||
Vale.Terms=No
|
||||
|
||||
|
||||
[formats]
|
||||
mdx = md
|
||||
5
packages/twenty-docs/README.md
Normal file
5
packages/twenty-docs/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Documentation
|
||||
|
||||
The docs here are managed through [Docusaurus 2](https://docusaurus.io/).
|
||||
|
||||
We recommend you go directly to the [statically generated website](https://docs.twenty.com) rather than read them here.
|
||||
3
packages/twenty-docs/babel.config.js
Normal file
3
packages/twenty-docs/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
||||
4
packages/twenty-docs/docs/contributor/_category_.json
Normal file
4
packages/twenty-docs/docs/contributor/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Contributor guide",
|
||||
"position": 2
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"position": 3
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Advanced",
|
||||
"position": 3
|
||||
}
|
||||
@ -0,0 +1,330 @@
|
||||
---
|
||||
title: Best Practices
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbChecklist
|
||||
---
|
||||
|
||||
This document outlines the best practices you should follow when working on the frontend.
|
||||
|
||||
## State management
|
||||
|
||||
React and Recoil handle state management in the codebase.
|
||||
|
||||
### Use `useRecoilState` to store state
|
||||
|
||||
It's good practice to create as many atoms as you need to store your state.
|
||||
|
||||
:::tip
|
||||
|
||||
It's better to use extra atoms than trying to be too concise with props drilling.
|
||||
|
||||
:::
|
||||
|
||||
```tsx
|
||||
export const myAtomState = atom({
|
||||
key: 'myAtomState',
|
||||
default: 'default value',
|
||||
});
|
||||
|
||||
export const MyComponent = () => {
|
||||
const [myAtom, setMyAtom] = useRecoilState(myAtomState);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
value={myAtom}
|
||||
onChange={(e) => setMyAtom(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Do not use `useRef` to store state
|
||||
|
||||
Avoid using `useRef` to store state.
|
||||
|
||||
If you want to store state, you should use `useState` or `useRecoilState`.
|
||||
|
||||
See [how to manage re-renders](#managing-re-renders) if you feel like you need `useRef` to prevent some re-renders from happening.
|
||||
|
||||
## Managing re-renders
|
||||
|
||||
Re-renders can be hard to manage in React.
|
||||
|
||||
Here are some rules to follow to avoid unnecessary re-renders.
|
||||
|
||||
Keep in mind that you can **always** avoid re-renders by understanding their cause.
|
||||
|
||||
### Work at the root level
|
||||
|
||||
Avoiding re-renders in new features is now made easy by eliminating them at the root level.
|
||||
|
||||
The `PageChangeEffect` sidecar component contains just one `useEffect` that holds all the logic to execute on a page change.
|
||||
|
||||
That way you know that there's just one place that can trigger a re-render.
|
||||
|
||||
### Always think twice before adding `useEffect` in your codebase
|
||||
|
||||
Re-renders are often caused by unnecessary `useEffect`.
|
||||
|
||||
You should think whether you need `useEffect`, or if you can move the logic in a event handler function.
|
||||
|
||||
You'll find it generally easy to move the logic in a `handleClick` or `handleChange` function.
|
||||
|
||||
You can also find them in libraries like Apollo: `onCompleted`, `onError`, etc.
|
||||
|
||||
### Use a sibling component to extract `useEffect` or data fetching logic
|
||||
|
||||
If you feel like you need to add a `useEffect` in your root component, you should consider extracting it in a sidecar component.
|
||||
|
||||
You can apply the same for data fetching logic, with Apollo hooks.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, will cause re-renders even if data is not changing,
|
||||
// because useEffect needs to be re-evaluated
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
const [someDependency] = useRecoilState(someDependencyState);
|
||||
|
||||
useEffect(() => {
|
||||
if(someDependency !== data) {
|
||||
setData(someDependency);
|
||||
}
|
||||
}, [someDependency]);
|
||||
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
|
||||
export const App = () => (
|
||||
<RecoilRoot>
|
||||
<PageComponent />
|
||||
</RecoilRoot>
|
||||
);
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, will not cause re-renders if data is not changing,
|
||||
// because useEffect is re-evaluated in another sibling component
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
|
||||
return <div>{data}</div>;
|
||||
};
|
||||
|
||||
export const PageData = () => {
|
||||
const [data, setData] = useRecoilState(dataState);
|
||||
const [someDependency] = useRecoilState(someDependencyState);
|
||||
|
||||
useEffect(() => {
|
||||
if(someDependency !== data) {
|
||||
setData(someDependency);
|
||||
}
|
||||
}, [someDependency]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export const App = () => (
|
||||
<RecoilRoot>
|
||||
<PageData />
|
||||
<PageComponent />
|
||||
</RecoilRoot>
|
||||
);
|
||||
```
|
||||
|
||||
### Use recoil family states and recoil family selectors
|
||||
|
||||
Recoil family states and selectors are a great way to avoid re-renders.
|
||||
|
||||
They are useful when you need to store a list of items.
|
||||
|
||||
### You shouldn't use `React.memo(MyComponent)`
|
||||
|
||||
Avoid using `React.memo()` because it does not solve the cause of the re-render, but instead breaks the re-render chain, which can lead to unexpected behavior and make the code very hard to refactor.
|
||||
|
||||
### Limit `useCallback` or `useMemo` usage
|
||||
|
||||
They are often not necessary and will make the code harder to read and maintain for a gain of performance that is unnoticeable.
|
||||
|
||||
## Console.logs
|
||||
|
||||
`console.log` statements are invaluable during development, offering real-time insights into variable values and code flow. But, leaving them in production code can lead to several issues:
|
||||
|
||||
1. **Performance**: Excessive logging can affect the runtime performance, specially on client-side applications.
|
||||
|
||||
2. **Security**: Logging sensitive data can expose critical information to anyone who inspects the browser's console.
|
||||
|
||||
3. **Cleanliness**: Filling up the console with logs can obscure important warnings or errors that developers or tools need to see.
|
||||
|
||||
4. **Professionalism**: End users or clients checking the console and seeing a myriad of log statements might question the code's quality and polish.
|
||||
|
||||
Make sure you remove all `console.logs` before pushing the code to production.
|
||||
|
||||
## Naming
|
||||
|
||||
### Variable Naming
|
||||
|
||||
Variable names ought to precisely depict the purpose or function of the variable.
|
||||
|
||||
#### The issue with generic names
|
||||
Generic names in programming are not ideal because they lack specificity, leading to ambiguity and reduced code readability. Such names fail to convey the variable or function's purpose, making it challenging for developers to understand the code's intent without deeper investigation. This can result in increased debugging time, higher susceptibility to errors, and difficulties in maintenance and collaboration. Meanwhile, descriptive naming makes the code self-explanatory and easier to navigate, enhancing code quality and developer productivity.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, uses a generic name that doesn't communicate its
|
||||
// purpose or content clearly
|
||||
const [value, setValue] = useState('');
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, uses a descriptive name
|
||||
const [email, setEmail] = useState('');
|
||||
```
|
||||
|
||||
#### Some words to avoid in variable names
|
||||
|
||||
- dummy
|
||||
|
||||
### Event handlers
|
||||
|
||||
Event handler names should start with `handle`, while `on` is a prefix used to name events in components props.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const onEmailChange = (val: string) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good
|
||||
const handleEmailChange = (val: string) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
## Optional Props
|
||||
|
||||
Avoid passing the default value for an optional prop.
|
||||
|
||||
**EXAMPLE**
|
||||
|
||||
Take the`EmailField` component defined below:
|
||||
|
||||
```tsx
|
||||
type EmailFieldProps = {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
|
||||
<TextInput value={value} disabled={disabled} fullWidth />
|
||||
);
|
||||
```
|
||||
|
||||
**Usage**
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, passing in the same value as the default value adds no value
|
||||
const Form = () => <EmailField value="username@email.com" disabled={false} />;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, assumes the default value
|
||||
const Form = () => <EmailField value="username@email.com" />;
|
||||
```
|
||||
|
||||
## Component as props
|
||||
|
||||
Try as much as possible to pass uninstantiated components as props, so children can decide on their own of what props they need to pass.
|
||||
|
||||
The most common example for that is icon components:
|
||||
|
||||
```tsx
|
||||
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
|
||||
|
||||
// In MyComponent
|
||||
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MyIcon size={theme.icon.size.md}>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
For React to understand that the component is a component, you need to use PascalCase, to later instantiate it with `<MyIcon>`
|
||||
|
||||
## Prop Drilling: Keep It Minimal
|
||||
|
||||
Prop drilling, in the React context, refers to the practice of passing state variables and their setters through many component layers, even if intermediary components don't use them. While sometimes necessary, excessive prop drilling can lead to:
|
||||
|
||||
1. **Decreased Readability**: Tracing where a prop originates or where it's utilized can become convoluted in a deeply nested component structure.
|
||||
|
||||
2. **Maintenance Challenges**: Changes in one component's prop structure might require adjustments in several components, even if they don't directly use the prop.
|
||||
|
||||
3. **Reduced Component Reusability**: A component receiving a lot of props solely for passing them down becomes less general-purpose and harder to reuse in different contexts.
|
||||
|
||||
If you feel that you are using excessive prop drilling, see [state management best practices](/contributor/frontend/advanced/best-practices#state-management).
|
||||
|
||||
## Imports
|
||||
|
||||
When importing, opt for the designated aliases rather than specifying complete or relative paths.
|
||||
|
||||
**The Aliases**
|
||||
|
||||
```js
|
||||
{
|
||||
alias: {
|
||||
"~": path.resolve(__dirname, "src"),
|
||||
"@": path.resolve(__dirname, "src/modules"),
|
||||
"@testing": path.resolve(__dirname, "src/testing"),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**
|
||||
```tsx
|
||||
// ❌ Bad, specifies the entire relative path
|
||||
import {
|
||||
CatalogDecorator
|
||||
} from '../../../../../testing/decorators/CatalogDecorator';
|
||||
import {
|
||||
ComponentDecorator
|
||||
} from '../../../../../testing/decorators/ComponentDecorator';
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, utilises the designated aliases
|
||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';=
|
||||
```
|
||||
|
||||
## Schema Validation
|
||||
|
||||
[Zod](https://github.com/colinhacks/zod) is the schema validator for untyped objects:
|
||||
|
||||
```js
|
||||
const validationSchema = z
|
||||
.object({
|
||||
exist: z.boolean(),
|
||||
email: z
|
||||
.string()
|
||||
.email('Email must be a valid email'),
|
||||
password: z
|
||||
.string()
|
||||
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
|
||||
})
|
||||
.required();
|
||||
|
||||
type Form = z.infer<typeof validationSchema>;
|
||||
```
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
Always perform thorough manual testing before proceeding to guarantee that modifications haven’t caused disruptions elsewhere, given that tests have not yet been extensively integrated.
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Hotkeys
|
||||
sidebar_position: 11
|
||||
sidebar_custom_props:
|
||||
icon: TbKeyboard
|
||||
---
|
||||
|
||||
You can intercept any hotkey combination and execute a custom action.
|
||||
|
||||
There's 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.
|
||||
|
||||
There's also a wrapper hook `useScopedHotkeys` that makes it easy to manage scopes.
|
||||
|
||||
```ts
|
||||
useScopedHotkeys(
|
||||
'ctrl+k,meta+k',
|
||||
() => {
|
||||
openCommandMenu();
|
||||
},
|
||||
AppHotkeyScope.CommandMenu,
|
||||
[openCommandMenu],
|
||||
);
|
||||
```
|
||||
@ -0,0 +1,291 @@
|
||||
---
|
||||
title: Style Guide
|
||||
sidebar_position: 4
|
||||
sidebar_custom_props:
|
||||
icon: TbPencil
|
||||
---
|
||||
|
||||
This document includes the rules to follow when writing code.
|
||||
|
||||
The goal here is to have a consistent codebase, which is easy to read and easy to maintain.
|
||||
|
||||
For this, it's better to be a bit more verbose than to be too concise.
|
||||
|
||||
Always keep in mind that people read code more often than they write it, specially on an open source project, where anyone can contribute.
|
||||
|
||||
There are a lot of rules that are not defined here, but that are automatically checked by linters.
|
||||
|
||||
## React
|
||||
|
||||
### Use functional components
|
||||
|
||||
Always use TSX functional components.
|
||||
|
||||
Do not use default `import` with `const`, because it's harder to read and harder to import with code completion.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, harder to read, harder to import with code completion
|
||||
const MyComponent = () => {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
|
||||
export default MyComponent;
|
||||
|
||||
// ✅ Good, easy to read, easy to import with code completion
|
||||
export function MyComponent() {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
Create the type of the props and call it `(ComponentName)Props` if there's no need to export it.
|
||||
|
||||
Use props destructuring.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, no type
|
||||
export const MyComponent = (props) => <div>Hello {props.name}</div>;
|
||||
|
||||
// ✅ Good, type
|
||||
type MyComponentProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
|
||||
```
|
||||
|
||||
#### Refrain from using `React.FC` or `React.FunctionComponent` to define prop types
|
||||
|
||||
```tsx
|
||||
/* ❌ - Bad, defines the component type annotations with `FC`
|
||||
* - With `React.FC`, the component implicitly accepts a `children` prop
|
||||
* even if it's not defined in the prop type. This might not always be
|
||||
* desirable, especially if the component doesn't intend to render
|
||||
* children.
|
||||
*/
|
||||
const EmailField: React.FC<{
|
||||
value: string;
|
||||
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
|
||||
```
|
||||
|
||||
```tsx
|
||||
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
|
||||
* component's props
|
||||
* - This method doesn't automatically include the children prop. If
|
||||
* you want to include it, you have to specify it in OwnProps.
|
||||
*/
|
||||
type EmailFieldProps = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
const EmailField = ({ value }: EmailFieldProps) => (
|
||||
<TextInput value={value} disabled fullWidth />
|
||||
);
|
||||
```
|
||||
|
||||
#### No Single Variable Prop Spreading in JSX Elements
|
||||
|
||||
Avoid using single variable prop spreading in JSX elements, like `{...props}`. This practice often results in code that is less readable and harder to maintain because it's unclear which props the component is receiving.
|
||||
|
||||
```tsx
|
||||
/* ❌ - Bad, spreads a single variable prop into the underlying component
|
||||
*/
|
||||
const MyComponent = (props: OwnProps) => {
|
||||
return <OtherComponent {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
/* ✅ - Good, Explicitly lists all props
|
||||
* - Enhances readability and maintainability
|
||||
*/
|
||||
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
|
||||
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
|
||||
};
|
||||
```
|
||||
|
||||
Rationale:
|
||||
- At a glance, it's clearer which props the code passes down, making it easier to understand and maintain.
|
||||
- It helps to prevent tight coupling between components via their props.
|
||||
- Linting tools make it easier to identify misspelled or unused props when you list props explicitly.
|
||||
|
||||
## JavaScript
|
||||
|
||||
### Use nullish-coalescing operator `??`
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, can return 'default' even if value is 0 or ''
|
||||
const value = process.env.MY_VALUE || 'default';
|
||||
|
||||
// ✅ Good, will return 'default' only if value is null or undefined
|
||||
const value = process.env.MY_VALUE ?? 'default';
|
||||
```
|
||||
|
||||
### Use optional chaining `?.`
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
onClick && onClick();
|
||||
|
||||
// ✅ Good
|
||||
onClick?.();
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
### Use `type` instead of `interface`
|
||||
|
||||
Always use `type` instead of `interface`, because they almost always overlap, and `type` is more flexible.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
interface MyInterface {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// ✅ Good
|
||||
type MyType = {
|
||||
name: string;
|
||||
};
|
||||
```
|
||||
|
||||
### Use string literals instead of enums
|
||||
|
||||
[String literals](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) are the go-to way to handle enum-like values in TypeScript. They are easier to extend with Pick and Omit, and offer a better developer experience, specially with code completion.
|
||||
|
||||
You can see why TypeScript recommends avoiding enums [here](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, utilizes an enum
|
||||
enum Color {
|
||||
Red = "red",
|
||||
Green = "green",
|
||||
Blue = "blue",
|
||||
}
|
||||
|
||||
let color = Color.Red;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, utilizes a string literal
|
||||
let color: "red" | "green" | "blue" = "red";
|
||||
```
|
||||
|
||||
#### GraphQL and internal libraries
|
||||
|
||||
You should use enums that GraphQL codegen generates.
|
||||
|
||||
It's also better to use an enum when using an internal library, so the internal library doesn't have to expose a string literal type that is not related to the internal API.
|
||||
|
||||
Example:
|
||||
|
||||
```TSX
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
RelationPickerHotkeyScope.RelationPicker,
|
||||
);
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
### Use StyledComponents
|
||||
|
||||
Style the components with [styled-components](https://emotion.sh/docs/styled).
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
<div className="my-class">Hello World</div>
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good
|
||||
const StyledTitle = styled.div`
|
||||
color: red;
|
||||
`;
|
||||
```
|
||||
|
||||
Prefix styled components with "Styled" to differentiate them from "real" components.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const Title = styled.div`
|
||||
color: red;
|
||||
`;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good
|
||||
const StyledTitle = styled.div`
|
||||
color: red;
|
||||
`;
|
||||
```
|
||||
|
||||
### Theming
|
||||
|
||||
Utilizing the theme for the majority of component styling is the preferred approach.
|
||||
|
||||
#### Units of measurement
|
||||
|
||||
Avoid using `px` or `rem` values directly within the styled components. The necessary values are generally already defined in the theme, so it’s recommended to make use of the theme for these purposes.
|
||||
|
||||
#### Colors
|
||||
|
||||
Refrain from introducing new colors; instead, use the existing palette from the theme. Should there be a situation where the palette does not align, please leave a comment so that the team can rectify it.
|
||||
|
||||
|
||||
```tsx
|
||||
// ❌ Bad, directly specifies style values without utilizing the theme
|
||||
const StyledButton = styled.button`
|
||||
color: #333333;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
margin-left: 4px;
|
||||
border-radius: 50px;
|
||||
`;
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Good, utilizes the theme
|
||||
const StyledButton = styled.button`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
border-radius: ${({ theme }) => theme.border.rounded};
|
||||
`;
|
||||
```
|
||||
## Enforcing No-Type Imports
|
||||
|
||||
Avoid type imports. To enforce this standard, an ESLint rule checks for and reports any type imports. This helps maintain consistency and readability in the TypeScript code.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
|
||||
// ❌ Bad
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
// ✅ Good
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
```
|
||||
|
||||
### Why No-Type Imports
|
||||
|
||||
- **Consistency**: By avoiding type imports and using a single approach for both type and value imports, the codebase remains consistent in its module import style.
|
||||
|
||||
- **Readability**: No-type imports improve code readability by making it clear when you're importing values or types. This reduces ambiguity and makes it easier to understand the purpose of imported symbols.
|
||||
|
||||
- **Maintainability**: It enhances codebase maintainability because developers can identify and locate type-only imports when reviewing or modifying code.
|
||||
|
||||
### ESLint Rule
|
||||
|
||||
An ESLint rule, `@typescript-eslint/consistent-type-imports`, enforces the no-type import standard. This rule will generate errors or warnings for any type import violations.
|
||||
|
||||
Please note that this rule specifically addresses rare edge cases where unintentional type imports occur. TypeScript itself discourages this practice, as mentioned in the [TypeScript 3.8 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). In most situations, you should not need to use type-only imports.
|
||||
|
||||
To ensure your code complies with this rule, make sure to run ESLint as part of your development workflow.
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Basics",
|
||||
"position": 1
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Basics
|
||||
sidebar_position: 0
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
<DocCardList />
|
||||
|
||||
## Tech Stack
|
||||
|
||||
The project has a clean and simple stack, with minimal boilerplate code.
|
||||
|
||||
**App**
|
||||
|
||||
- [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)
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
|
||||
**Testing**
|
||||
|
||||
- [Jest](https://jestjs.io/)
|
||||
- [Storybook](https://storybook.js.org/)
|
||||
|
||||
**Tooling**
|
||||
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- [Craco](https://craco.js.org/docs/)
|
||||
- [ESLint](https://eslint.org/)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Routing
|
||||
|
||||
[React Router](https://reactrouter.com/) handles the routing.
|
||||
|
||||
To avoid unnecessary [re-renders](/contributor/frontend/advanced/best-practices#managing-re-renders) all the routing logic is in a `useEffect` in `PageChangeEffect`.
|
||||
|
||||
### State Management
|
||||
|
||||
[Recoil](https://recoiljs.org/docs/introduction/core-concepts) handles state management.
|
||||
|
||||
See [best practices](/contributor/frontend/advanced/best-practices#state-management) for more information on state management.
|
||||
|
||||
## Testing
|
||||
|
||||
[Jest](https://jestjs.io/) serves as the tool for unit testing while [Storybook](https://storybook.js.org/) is for component testing.
|
||||
|
||||
Jest is mainly for testing utility functions, and not components themselves.
|
||||
|
||||
Storybook is for testing the behavior of isolated components, as well as displaying the [design system](/contributor/frontend/basics/design-system).
|
||||
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Contributing
|
||||
sidebar_position: 1
|
||||
description: Learn how you can contribute to the project
|
||||
sidebar_custom_props:
|
||||
icon: TbTopologyStar
|
||||
---
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
Make sure that your [IDE is correctly setup](/contributor/local-setup/ide-setup) and that your backend is running on `localhost:3000`.
|
||||
|
||||
|
||||
## Starting a new feature
|
||||
|
||||
Make sure your database is running on the URL provided in your `server/.env` file.
|
||||
|
||||
```bash
|
||||
cd front
|
||||
yarn
|
||||
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Regenerate graphql schema based on API graphql schema
|
||||
|
||||
```bash
|
||||
yarn graphql:generate
|
||||
```
|
||||
|
||||
## Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
yarn test # run jest tests
|
||||
yarn storybook:dev # run storybook
|
||||
yarn storybook:test # run tests (needs yarn storybook:dev to be running)
|
||||
yarn storybook:coverage # run tests (needs yarn storybook:dev to be running)
|
||||
```
|
||||
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Design System
|
||||
description: What our design system looks like
|
||||
sidebar_position: 7
|
||||
sidebar_custom_props:
|
||||
icon: TbPaint
|
||||
---
|
||||
|
||||
The CRM depends on its internal and custom design system, constructed on top of styled-components.
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
---
|
||||
title: Folder Architecture
|
||||
sidebar_position: 5
|
||||
description: A detailed look into our folder architecture
|
||||
sidebar_custom_props:
|
||||
icon: TbFolder
|
||||
---
|
||||
|
||||
In this guide, you will explore the details of the project directory structure and how it contributes to the organization and maintainability of Twenty.
|
||||
|
||||
By following this folder architecture convention, it's easier to find the files related to specific features and ensure that the application is scalable and maintainable.
|
||||
|
||||
```
|
||||
front
|
||||
└───modules
|
||||
│ └───module1
|
||||
│ │ └───submodule1
|
||||
│ └───module2
|
||||
│ └───ui
|
||||
│ │ └───display
|
||||
│ │ └───inputs
|
||||
│ │ │ └───buttons
|
||||
│ │ └───...
|
||||
└───pages
|
||||
└───...
|
||||
```
|
||||
|
||||
## Pages
|
||||
|
||||
Includes the top-level components defined by the application routes. They import more low-level components from the modules folder (more details below).
|
||||
|
||||
## Modules
|
||||
|
||||
Each module represents a feature or a group of feature, comprising its specific components, states, and operational logic.
|
||||
They should all follow the structure below. You can nest modules within modules (referred to as submodules) and the same rules will apply.
|
||||
|
||||
```
|
||||
module1
|
||||
└───components
|
||||
│ └───component1
|
||||
│ └───component2
|
||||
└───constants
|
||||
└───contexts
|
||||
└───graphql
|
||||
│ └───fragments
|
||||
│ └───queries
|
||||
│ └───mutations
|
||||
└───hooks
|
||||
│ └───internal
|
||||
└───states
|
||||
│ └───selectors
|
||||
└───types
|
||||
└───utils
|
||||
```
|
||||
|
||||
### Contexts
|
||||
|
||||
A context is a way to pass data through the component tree without having to pass props down manually at every level.
|
||||
|
||||
See [React Context](https://react.dev/reference/react#context-hooks) for more details.
|
||||
|
||||
### GraphQL
|
||||
|
||||
Includes fragments, queries, and mutations.
|
||||
|
||||
See [GraphQL](https://graphql.org/learn/) for more details.
|
||||
|
||||
- Fragments
|
||||
|
||||
A fragment is a reusable piece of a query, which you can use in different places. By using fragments, it's easier to avoid duplicating code.
|
||||
|
||||
See [GraphQL Fragments](https://graphql.org/learn/queries/#fragments) for more details.
|
||||
|
||||
- Queries
|
||||
|
||||
See [GraphQL Queries](https://graphql.org/learn/queries/) for more details.
|
||||
|
||||
- Mutations
|
||||
|
||||
See [GraphQL Mutations](https://graphql.org/learn/queries/#mutations) for more details.
|
||||
|
||||
### Hooks
|
||||
|
||||
See [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) for more details.
|
||||
|
||||
### States
|
||||
|
||||
Contains the state management logic. [RecoilJS](https://recoiljs.org) handles this.
|
||||
|
||||
- Selectors: See [RecoilJS Selectors](https://recoiljs.org/docs/basic-tutorial/selectors) for more details.
|
||||
|
||||
React's built-in state management still handles state within a component.
|
||||
|
||||
### Utils
|
||||
|
||||
Should just contain reusable pure functions. Otherwise, create custom hooks in the `hooks` folder.
|
||||
|
||||
|
||||
## UI
|
||||
|
||||
Contains all the reusable UI components used in the application.
|
||||
|
||||
This folder can contain sub-folders, like `data`, `display`, `feedback`, and `input` for specific types of components. Each component should be self-contained and reusable, so that you can use it in different parts of the application.
|
||||
|
||||
By separating the UI components from the other components in the `modules` folder, it's easier to maintain a consistent design and to make changes to the UI without affecting other parts (business logic) of the codebase.
|
||||
|
||||
## Interface and dependencies
|
||||
|
||||
You can import other module code from any module except for the `ui` folder. This will keep its code easy to test.
|
||||
|
||||
### Internal
|
||||
|
||||
Each part (hooks, states, ...) of a module can have an `internal` folder, which contains parts that are just used within the module.
|
||||
@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Work with Figma
|
||||
description: Learn how you can collaborate with Twenty's Figma
|
||||
sidebar_position: 2
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandFigma
|
||||
---
|
||||
|
||||
Figma is a collaborative interface design tool that aids in bridging the communication barrier between designers and developers.
|
||||
This guide explains how you can collaborate with Figma.
|
||||
|
||||
## Access
|
||||
|
||||
1. **Access the shared link:** You can access the project's Figma file [here](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
|
||||
2. **Sign in:** If you're not already signed in, Figma will prompt you to do so.
|
||||
Key features are only available to logged-in users, such as the developer mode and the ability to select a dedicated frame.
|
||||
|
||||
:::caution Note
|
||||
|
||||
You will not be able to collaborate effectively without an account.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## Figma structure
|
||||
|
||||
On the left sidebar, you can access the different pages of Twenty's Figma. This is how they're organized:
|
||||
|
||||
- **Components page:** This is the first page. The designer uses it to create and organize the reusable design elements used throughout the design file. For example, buttons, icons, symbols, or any other reusable components. It serves to maintain consistency across the design.
|
||||
- **Main page:** The second page is the main page, which shows the complete user interface of the project. You can press ***Play*** to use the full app prototype.
|
||||
- **Features pages:** The other pages are typically dedicated to features in progress. They contain the design of specific features or modules of the application or website. They are typically still in progress.
|
||||
|
||||
## Useful Tips
|
||||
|
||||
With read-only access, you can't edit the design but you can access all features that will be useful to convert the designs into code.
|
||||
|
||||
### Use the Dev mode
|
||||
|
||||
Figma's Dev Mode enhances developers' productivity by providing easy design navigation, effective asset management, efficient communication tools, toolbox integrations, quick code snippets, and key layer information, bridging the gap between design and development. You can learn more about Dev Mode [here](https://www.figma.com/dev-mode/).
|
||||
|
||||
Switch to the "Developer" mode in the right part of the toolbar to see design specs, copy CSS, and access assets.
|
||||
|
||||
### Use the Prototype
|
||||
|
||||
Click on any element on the canvas and press the “Play” button at the top right edge of the interface to access the prototype view. Prototype mode allows you to interact with the design as if it were the final product. It demonstrates the flow between screens and how interface elements like buttons, links, or menus behave when interacted with.
|
||||
|
||||
1. **Understanding transitions and animations:** In the Prototype mode, you can view any transitions or animations added by a designer between screens or UI elements, providing clear visual instructions to developers on the intended behavior and style.
|
||||
2. **Implementation clarification:** A prototype can also help reduce ambiguities. Developers can interact with it to gain a better understanding of the functionality or appearance of particular elements.
|
||||
|
||||
For more comprehensive details and guidance on learning the Figma platform, you can visit the official [Figma Documentation](https://help.figma.com/hc/en-us).
|
||||
|
||||
### Measure distances
|
||||
|
||||
Select an element, hold `Option` key (Mac) or `Alt` key (Windows), then hover over another element to see the distance between them.
|
||||
|
||||
### Figma extension for VSCode (Recommended)
|
||||
|
||||
[Figma for VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
|
||||
lets you navigate and inspect design files, collaborate with designers, track changes, and speed up implementation - all without leaving your text editor.
|
||||
It's part of our recommended extensions.
|
||||
|
||||
## Collaboration
|
||||
|
||||
1. **Using Comments:** You are welcome to use the comment feature by clicking on the bubble icon in the left part of the toolbar.
|
||||
2. **Cursor chat:** A nice feature of Figma is the Cursor chat. Just press `;` on Mac and `/` on Windows to send a message if you see someone else using Figma as the same time as you.
|
||||
14
packages/twenty-docs/docs/contributor/frontend/frontend.mdx
Normal file
14
packages/twenty-docs/docs/contributor/frontend/frontend.mdx
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
id: frontend
|
||||
title: Frontend Development
|
||||
displayed_sidebar: frontendSidebar
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbTerminal2
|
||||
isSidebarRoot: true
|
||||
---
|
||||
|
||||
Welcome to the Frontend Development section of the documentation.
|
||||
Here you will find information about the frontend development process, the recommended tools, and the best practices you should follow.
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "UI Components",
|
||||
"position": 1
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "Display",
|
||||
"position": 1,
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"customProps": {
|
||||
"icon": "TbAppWindow"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
---
|
||||
title: App Tooltip
|
||||
sidebar_position: 6
|
||||
sidebar_custom_props:
|
||||
icon: TbTooltip
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import appTooltipCode from '!!raw-loader!@site/src/ui/display/appTooltipCode.js'
|
||||
import overflowingTextWithTooltipCode from '!!raw-loader!@site/src/ui/display/overflowingTextWithTooltipCode.js'
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
A brief message that displays additional information when a user interacts with an element.
|
||||
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/tooltip/AppTooltip']}
|
||||
componentCode={appTooltipCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional CSS class for additional styling</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>anchorSelect</td>
|
||||
<td>CSS selector</td>
|
||||
<td>Selector for the tooltip anchor (the element that triggers the tooltip)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>content</td>
|
||||
<td>string</td>
|
||||
<td>The content you want to display within the tooltip</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>delayHide</td>
|
||||
<td>number</td>
|
||||
<td>The delay in seconds before hiding the tooltip after the cursor leaves the anchor</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>offset</td>
|
||||
<td>number</td>
|
||||
<td>The offset in pixels for positioning the tooltip</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>noArrow</td>
|
||||
<td>boolean</td>
|
||||
<td>If `true`, hides the arrow on the tooltip</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>isOpen</td>
|
||||
<td>boolean</td>
|
||||
<td>If `true`, the tooltip is open by default</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>place</td>
|
||||
<td>`PlacesType` string from `react-tooltip`</td>
|
||||
<td>Specifies the placement of the tooltip. Values include `bottom`, `left`, `right`, `top`, `top-start`, `top-end`, `right-start`, `right-end`, `bottom-start`, `bottom-end`, `left-start`, and `left-end`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>positionStrategy</td>
|
||||
<td>`PositionStrategy` string from `react-tooltip`</td>
|
||||
<td>Position strategy for the tooltip. Has two values: `absolute` and `fixed`</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Overflowing Text with Tooltip
|
||||
|
||||
Handles overflowing text and displays a tooltip when the text overflows.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/tooltip/OverflowingTextWithTooltip']}
|
||||
componentCode={overflowingTextWithTooltipCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>text</td>
|
||||
<td>string</td>
|
||||
<td>The content you want to display in the overflowing text area</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Checkmark
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbCheck
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import checkmarkCode from '!!raw-loader!@site/src/ui/display/checkmarkCode.js'
|
||||
import animatedCheckmarkCode from '!!raw-loader!@site/src/ui/display/animatedCheckmarkCode.js'
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
Represents a successful or completed action.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/checkmark/components/Checkmark']}
|
||||
componentCode={checkmarkCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
Extends `React.ComponentPropsWithoutRef<'div'>` and accepts all the props of a regular `div` element.
|
||||
|
||||
</TabItem >
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Animated Checkmark
|
||||
|
||||
Represents a checkmark icon with the added feature of animation.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/checkmark/components/AnimatedCheckmark']}
|
||||
componentCode={animatedCheckmarkCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props" default>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>isAnimating</td>
|
||||
<td>boolean</td>
|
||||
<td>Controls whether the checkmark is animating</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>color</td>
|
||||
<td>string</td>
|
||||
<td>Color of the checkmark</td>
|
||||
<td>Theme's gray0</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>duration</td>
|
||||
<td>number</td>
|
||||
<td>The duration of the animation in seconds</td>
|
||||
<td>0.5 seconds</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>number</td>
|
||||
<td>The size of the checkmark</td>
|
||||
<td>28 pixels</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,252 @@
|
||||
---
|
||||
title: Chip
|
||||
sidebar_position: 2
|
||||
sidebar_custom_props:
|
||||
icon: TbLayoutList
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import chipCode from '!!raw-loader!@site/src/ui/display/chipCode.js'
|
||||
import entityChipCode from '!!raw-loader!@site/src/ui/display/entityChipCode.js'
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
A visual element that you can use as a clickable or non-clickable container with a label, optional left and right components, and various styling options to display labels and tags.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/chip/components/Chip']}
|
||||
componentCode={chipCode}
|
||||
/>
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>`ChipSize` enum</td>
|
||||
<td>Specifies the size of the chip. Has two options: `large` and `small`</td>
|
||||
<td>small</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether the chip is disabled</td>
|
||||
<td>false</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>clickable</td>
|
||||
<td>boolean</td>
|
||||
<td>Specifies whether the chip is clickable</td>
|
||||
<td>true</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>label</td>
|
||||
<td>string</td>
|
||||
<td>Represents the text content or label inside the chip</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>maxWidth</td>
|
||||
<td>string</td>
|
||||
<td>Specifies the maximum width of the chip</td>
|
||||
<td>200px</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>`ChipVariant` enum</td>
|
||||
<td>Specifies the visual style or variant of the chip. Has four options: `regular`, `highlighted`, `transparent`, and `rounded`</td>
|
||||
<td>regular</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>accent</td>
|
||||
<td>`ChipAccent` enum</td>
|
||||
<td>Determines the text color or accent color of the chip. Has two options: `text-primary` and `text-secondary`</td>
|
||||
<td>text-primary</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>leftComponent</td>
|
||||
<td>`React.ReactNode`</td>
|
||||
<td>An optional React/node component that you can place on the left side of the chip</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>rightComponent</td>
|
||||
<td>`React.ReactNode`</td>
|
||||
<td>An optional React/node component that you can place on the right side of the chip</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>An optional class name to apply additional custom styles to the chip</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Examples
|
||||
|
||||
### Transparent Disabled Chip
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/chip/components/Chip']}
|
||||
componentCode={`import { Chip } from "@/ui/display/chip/components/Chip";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Chip
|
||||
size="large"
|
||||
label="Transparent Disabled Chip"
|
||||
clickable={false}
|
||||
variant="rounded"
|
||||
accent="text-secondary"
|
||||
leftComponent
|
||||
rightComponent
|
||||
maxWidth="200px"
|
||||
className
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
<br/>
|
||||
|
||||
### Disabled Chip with Tooltip
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/chip/components/Chip']}
|
||||
componentCode={`import { Chip } from "@/ui/display/chip/components/Chip";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<Chip
|
||||
size="large"
|
||||
label="This is a very long label for a disabled chip that triggers a tooltip when overflowing."
|
||||
clickable={false}
|
||||
variant="regular"
|
||||
accent="text-primary"
|
||||
leftComponent
|
||||
rightComponent
|
||||
maxWidth="200px"
|
||||
className
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
`}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
## Entity Chip
|
||||
|
||||
A Chip-like element to display information about an entity.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/chip/components/EntityChip', '@/ui/display/icon/types/IconComponent']}
|
||||
componentCode={entityChipCode}
|
||||
/>
|
||||
</TabItem >
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>linkToEntity</td>
|
||||
<td>string</td>
|
||||
<td>The link to the entity</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>entityId</td>
|
||||
<td>string</td>
|
||||
<td>The unique identifier for the entity</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>string</td>
|
||||
<td>The name of the entity</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>pictureUrl</td>
|
||||
<td>string</td>
|
||||
<td>The URL of the entity's picture</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>avatarType</td>
|
||||
<td>Avatar Type</td>
|
||||
<td>The type of avatar you want to display. Has two options: `rounded` and `squared`</td>
|
||||
<td>rounded</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>`EntityChipVariant` enum</td>
|
||||
<td>Variant of the entity chip you want to display. Has two options: `regular` and `transparent`</td>
|
||||
<td>regular</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>LeftIcon</td>
|
||||
<td>IconComponent</td>
|
||||
<td>A React component representing an icon. Displayed on the left side of the chip</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@ -0,0 +1,134 @@
|
||||
---
|
||||
title: Icons
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbIcons
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import tablerIconExampleCode from '!!raw-loader!@site/src/ui/display/tablerIconExampleCode.js'
|
||||
import iconAddressBookCode from '!!raw-loader!@site/src/ui/display/iconAddressBookCode.js'
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
A list of icons used throughout our app.
|
||||
|
||||
## Tabler Icons
|
||||
|
||||
We use Tabler icons for React throughout the app.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="installation" label="Installation">
|
||||
|
||||
```
|
||||
yarn add @tabler/icons-react
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
You can import each icon as a component. Here's an example:
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/icon/components/IconAddressBook']}
|
||||
componentCode={tablerIconExampleCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>number</td>
|
||||
<td>The height and width of the icon in pixels</td>
|
||||
<td>24</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>color</td>
|
||||
<td>string</td>
|
||||
<td>The color of the icons</td>
|
||||
<td>currentColor</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>stroke</td>
|
||||
<td>number</td>
|
||||
<td>The stroke width of the icon in pixels</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Custom Icons
|
||||
|
||||
In addition to Tabler icons, the app also uses some custom icons.
|
||||
|
||||
### Icon Address Book
|
||||
|
||||
Displays an address book icon.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/icon/components/IconAddressBook']}
|
||||
componentCode={iconAddressBookCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>number</td>
|
||||
<td>The height and width of the icon in pixels</td>
|
||||
<td>24</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>stroke</td>
|
||||
<td>number</td>
|
||||
<td>The stroke width of the icon in pixels</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Soon Pill
|
||||
sidebar_position: 4
|
||||
sidebar_custom_props:
|
||||
icon: TbPill
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import soonPillCode from '!!raw-loader!@site/src/ui/display/soonPillCode.js'
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
A small badge or "pill" to indicate something is coming soon.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/pill/components/SoonPill']}
|
||||
componentCode={soonPillCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Tag
|
||||
sidebar_position: 5
|
||||
sidebar_custom_props:
|
||||
icon: TbTag
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import tagCode from '!!raw-loader!@site/src/ui/display/tagCode.js'
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
Component to visually categorize or label content.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/display/tag/components/Tag']}
|
||||
componentCode={tagCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional name for additional styling</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>color</td>
|
||||
<td>string</td>
|
||||
<td>Color of the tag. Options include: `green`, `turquoise`, `sky`, `blue`, `purple`, `pink`, `red`, `orange`, `yellow`, `gray`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>text</td>
|
||||
<td>string</td>
|
||||
<td>The content of the tag</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onClick</td>
|
||||
<td>function</td>
|
||||
<td>Optional function called when a user clicks on the tag</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "Feedback",
|
||||
"position": 2,
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"customProps": {
|
||||
"icon": "TbForms"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
---
|
||||
title: Progress Bar
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbLoader2
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import progressBarCode from '!!raw-loader!@site/src/ui/feedback/progressBarCode.js'
|
||||
import circularProgressBarCode from '!!raw-loader!@site/src/ui/feedback/circularProgressBarCode.js'
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
Indicates progress or countdown and moves from right to left.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/feedback/progress-bar/components/ProgressBar']}
|
||||
componentCode={progressBarCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>duration</td>
|
||||
<td>number</td>
|
||||
<td>The total duration of the progress bar animation in milliseconds</td>
|
||||
<td>3</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>delay</td>
|
||||
<td>number</td>
|
||||
<td>The delay in starting the progress bar animation in milliseconds</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>easing</td>
|
||||
<td>string</td>
|
||||
<td>Easing function for the progress bar animation</td>
|
||||
<td>easeInOut</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>barHeight</td>
|
||||
<td>number</td>
|
||||
<td>The height of the bar in pixels</td>
|
||||
<td>24</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>barColor</td>
|
||||
<td>string</td>
|
||||
<td>The color of the bar</td>
|
||||
<td>gray80</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>autoStart</td>
|
||||
<td>boolean</td>
|
||||
<td>If `true`, the progress bar animation starts automatically when the component mounts</td>
|
||||
<td>`true`</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Circular Progress Bar
|
||||
|
||||
Indicates the progress of a task, often used in loading screens or areas where you want to communicate ongoing processes to the user.
|
||||
|
||||
<Tabs>
|
||||
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/feedback/progress-bar/components/CircularProgressBar']}
|
||||
componentCode={circularProgressBarCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>number</td>
|
||||
<td>The size of the circular progress bar</td>
|
||||
<td>50</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>barWidth</td>
|
||||
<td>number</td>
|
||||
<td>The width of the progress bar line</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>barColor</td>
|
||||
<td>string</td>
|
||||
<td>The color of the progress bar</td>
|
||||
<td>currentColor</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "Input",
|
||||
"position": 3,
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"customProps": {
|
||||
"icon": "TbInputSearch"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,798 @@
|
||||
---
|
||||
title: Buttons
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbSquareRoundedPlusFilled
|
||||
---
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import buttonCode from '!!raw-loader!@site/src/ui/input/button/buttonCode.js'
|
||||
import buttonGroupCode from '!!raw-loader!@site/src/ui/input/button/buttonGroupCode.js'
|
||||
import floatingButtonCode from '!!raw-loader!@site/src/ui/input/button/floatingButtonCode.js'
|
||||
import floatingButtonGroupCode from '!!raw-loader!@site/src/ui/input/button/floatingButtonGroupCode.js'
|
||||
import floatingIconButtonCode from '!!raw-loader!@site/src/ui/input/button/floatingIconButtonCode.js'
|
||||
import floatingIconButtonGroupCode from '!!raw-loader!@site/src/ui/input/button/floatingIconButtonGroupCode.js'
|
||||
import lightButtonCode from '!!raw-loader!@site/src/ui/input/button/lightButtonCode.js'
|
||||
import lightIconButtonCode from '!!raw-loader!@site/src/ui/input/button/lightIconButtonCode.js'
|
||||
import mainButtonCode from '!!raw-loader!@site/src/ui/input/button/mainButtonCode.js'
|
||||
import roundedIconButtonCode from '!!raw-loader!@site/src/ui/input/button/roundedIconButtonCode.js'
|
||||
|
||||
A list of buttons and button groups used throughout the app.
|
||||
|
||||
## Button
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/Button']}
|
||||
componentCode={buttonCode}
|
||||
/>
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional class name for additional styling</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Icon</td>
|
||||
<td>`React.ComponentType`</td>
|
||||
<td>An optional icon component that's displayed within the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>title</td>
|
||||
<td>string</td>
|
||||
<td>The text content of the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>fullWidth</td>
|
||||
<td>boolean</td>
|
||||
<td>Defines whether the button should span the whole width of its container</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>string</td>
|
||||
<td>The visual style variant of the button. Options include `primary`, `secondary`, and `tertiary`</td>
|
||||
<td>primary</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the button. Has two options: `small` and `medium`</td>
|
||||
<td>medium</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>position</td>
|
||||
<td>string</td>
|
||||
<td>The position of the button in relation to its siblings. Options include: `standalone`, `left`, `right`, and `middle`</td>
|
||||
<td>standalone</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>accent</td>
|
||||
<td>string</td>
|
||||
<td>The accent color of the button. Options include: `default`, `blue`, and `danger`</td>
|
||||
<td>default</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>soon</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates if the button is marked as "soon" (such as for upcoming features)</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Specifies whether button is disabled or not</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>focus</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines if the button has focus</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onClick</td>
|
||||
<td>function</td>
|
||||
<td>A callback function that triggers when the user clicks on the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Button Group
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/Button', '@/ui/input/button/components/ButtonGroup']}
|
||||
componentCode={buttonGroupCode}
|
||||
/>
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>string</td>
|
||||
<td>The visual style variant of the buttons within the group. Options include `primary`, `secondary`, and `tertiary`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the buttons within the group. Has two options: `medium` and `small`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>accent</td>
|
||||
<td>string</td>
|
||||
<td>The accent color of the buttons within the group. Options include `default`, `blue` and `danger`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional class name for additional styling</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>children</td>
|
||||
<td>ReactNode</td>
|
||||
<td>An array of React elements representing the individual buttons within the group</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Floating Button
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/FloatingButton']}
|
||||
componentCode={floatingButtonCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional name for additional styling</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Icon</td>
|
||||
<td>`React.ComponentType`</td>
|
||||
<td>An optional icon component that's displayed within the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>title</td>
|
||||
<td>string</td>
|
||||
<td>The text content of the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the button. Has two options: `small` and `medium`</td>
|
||||
<td>small</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>position</td>
|
||||
<td>string</td>
|
||||
<td>The position of the button in relation to its sublings. Options include: `standalone`, `left`, `middle`, `right`</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>applyShadow</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether to apply shadow to a button</td>
|
||||
<td>`true`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>applyBlur</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether to apply a blur effect to the button</td>
|
||||
<td>`true`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether the button is disabled</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>focus</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates if the button has focus</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Floating Button Group
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/FloatingButton', '@/ui/input/button/components/FloatingButtonGroup']}
|
||||
componentCode={floatingButtonGroupCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the button. Has two options: `small` and `medium`</td>
|
||||
<td>small</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>children</td>
|
||||
<td>ReactNode</td>
|
||||
<td>An array of React elements representing the individual buttons within the group</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Floating Icon Button
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/FloatingIconButton']}
|
||||
componentCode={floatingIconButtonCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional name for additional styling</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Icon</td>
|
||||
<td>`React.ComponentType`</td>
|
||||
<td>An optional icon component that's displayed within the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the button. Has two options: `small` and `medium`</td>
|
||||
<td>small</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>position</td>
|
||||
<td>string</td>
|
||||
<td>The position of the button in relation to its siblings. Options include: `standalone`, `left`, `right`, and `middle`</td>
|
||||
<td>standalone</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>applyShadow</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether to apply shadow to a button</td>
|
||||
<td>`true`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>applyBlur</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether to apply a blur effect to the button</td>
|
||||
<td>`true`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether the button is disabled</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>focus</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates if the button has focus</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onClick</td>
|
||||
<td>function</td>
|
||||
<td>A callback function that triggers when the user clicks on the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>isActive</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines if the button is in an active state</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Floating Icon Button Group
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/FloatingIconButtonGroup']}
|
||||
componentCode={floatingIconButtonGroupCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional name for additional styling</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the button. Has two options: `small` and `medium`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>iconButtons</td>
|
||||
<td>array</td>
|
||||
<td>An array of objects, each representing an icon button in the group. Each object should include the icon component you want to display in the button, the function you want to call when a user clicks on the button, and whether the button should be active or not.</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Light Button
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/LightButton']}
|
||||
componentCode={lightButtonCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional name for additional styling</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>icon</td>
|
||||
<td>`React.ReactNode`</td>
|
||||
<td>The icon you want to display in the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>title</td>
|
||||
<td>string</td>
|
||||
<td>The text content of the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>accent</td>
|
||||
<td>string</td>
|
||||
<td>The accent color of the button. Options include: `secondary` and `tertiary`</td>
|
||||
<td>secondary</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>active</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines if the button is in an active state</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether the button is disabled</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>focus</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates if the button has focus</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onClick</td>
|
||||
<td>function</td>
|
||||
<td>A callback function that triggers when the user clicks on the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Light Icon Button
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/LightIconButton']}
|
||||
componentCode={lightIconButtonCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional name for additional styling</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>testId</td>
|
||||
<td>string</td>
|
||||
<td>Test identifier for the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Icon</td>
|
||||
<td>`React.ComponentType`</td>
|
||||
<td>An optional icon component that's displayed within the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>title</td>
|
||||
<td>string</td>
|
||||
<td>The text content of the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the button. Has two options: `small` and `medium`</td>
|
||||
<td>small</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>accent</td>
|
||||
<td>string</td>
|
||||
<td>The accent color of the button. Options include: `secondary` and `tertiary`</td>
|
||||
<td>secondary</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>active</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines if the button is in an active state</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Determines whether the button is disabled</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>focus</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates if the button has focus</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onClick</td>
|
||||
<td>function</td>
|
||||
<td>A callback function that triggers when the user clicks on the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Main Button
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/MainButton']}
|
||||
componentCode={mainButtonCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>title</td>
|
||||
<td>string</td>
|
||||
<td>The text content of the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>fullWidth</td>
|
||||
<td>boolean</td>
|
||||
<td>efines whether the button should span the whole width of its container</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>string</td>
|
||||
<td>The visual style variant of the button. Options include `primary` and `secondary`</td>
|
||||
<td>primary</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>soon</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates if the button is marked as "soon" (such as for upcoming features)</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Icon</td>
|
||||
<td>`React.ComponentType`</td>
|
||||
<td>An optional icon component that's displayed within the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>React `button` props</td>
|
||||
<td>`React.ComponentProps<'button'>`</td>
|
||||
<td>Additional props from React's `button` element</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Rounded Icon Button
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/button/components/RoundedIconButton']}
|
||||
componentCode={roundedIconButtonCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Icon</td>
|
||||
<td>`React.ComponentType`</td>
|
||||
<td>An optional icon component that's displayed within the button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>React `button` props</td>
|
||||
<td>`React.ButtonHTMLAttributes<HTMLButtonElement>`</td>
|
||||
<td>Additional props from React's `button` element</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Checkbox
|
||||
sidebar_position: 4
|
||||
sidebar_custom_props:
|
||||
icon: TbCheckbox
|
||||
---
|
||||
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import checkboxCode from '!!raw-loader!@site/src/ui/input/components/checkboxCode.js'
|
||||
|
||||
Used when a user needs to select multiple values from several options.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/Checkbox']}
|
||||
componentCode={checkboxCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>checked</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether the checkbox is checked</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>indeterminate</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether the checkbox is in an indeterminate state (neither checked nor unchecked)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The callback function you want to trigger when the checkbox state changes</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onCheckedChange</td>
|
||||
<td>function</td>
|
||||
<td>The callback function you want to trigger when the `checked` state changes</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>string</td>
|
||||
<td>The visual style variant of the box. Options include: `primary`, `secondary`, and `tertiary`</td>
|
||||
<td>primary</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the checkbox. Has two options: `small` and `large`</td>
|
||||
<td>small</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>shape</td>
|
||||
<td>string</td>
|
||||
<td>The shape of the checkbox. Has two options: `squared` and `rounded`</td>
|
||||
<td>squared</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,111 @@
|
||||
---
|
||||
title: Color Scheme
|
||||
sidebar_position: 2
|
||||
sidebar_custom_props:
|
||||
icon: TbColorFilter
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import colorSchemeCardCode from '!!raw-loader!@site/src/ui/input/color-scheme/colorSchemeCardCode.js'
|
||||
import colorSchemePickerCode from '!!raw-loader!@site/src/ui/input/color-scheme/colorSchemePickerCode.js'
|
||||
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
|
||||
## Color Scheme Card
|
||||
|
||||
Represents different color schemes and is specially tailored for light and dark themes.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/color-scheme/components/ColorSchemeCard']}
|
||||
componentCode={colorSchemeCardCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>string</td>
|
||||
<td>The color scheme variant. Options include `Dark`, `Light`, and `System`</td>
|
||||
<td>light</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>selected</td>
|
||||
<td>boolean</td>
|
||||
<td>If `true`, displays a checkmark to indicate the selected color scheme</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>additional props</td>
|
||||
<td>`React.ComponentPropsWithoutRef<'div'>`</td>
|
||||
<td>Standard HTML `div` element props</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Color Scheme Picker
|
||||
|
||||
Allows users to choose between different color schemes.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/color-scheme/components/ColorSchemePicker']}
|
||||
componentCode={colorSchemePickerCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>`Color Scheme`</td>
|
||||
<td>The currently selected color scheme</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The callback function you want to trigger when a user selects a color scheme</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Icon Picker
|
||||
sidebar_position: 5
|
||||
sidebar_custom_props:
|
||||
icon: TbColorPicker
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import iconPickerCode from '!!raw-loader!@site/src/ui/input/components/iconPickerCode.js'
|
||||
|
||||
A dropdown-based icon picker that allows users to select an icon from a list.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/IconPicker']}
|
||||
componentCode={iconPickerCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Disables the icon picker if set to `true`</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The callback function triggered when the user selects an icon. It receives an object with `iconKey` and `Icon` properties</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>selectedIconKey</td>
|
||||
<td>string</td>
|
||||
<td>The key of the initially selected icon</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onClickOutside</td>
|
||||
<td>function</td>
|
||||
<td>Callback function triggered when the user clicks outside the dropdown</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onClose</td>
|
||||
<td>function</td>
|
||||
<td>Callback function triggered when the dropdown is closed</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onOpen</td>
|
||||
<td>function</td>
|
||||
<td>Callback function triggered when the dropdown is opened</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>string</td>
|
||||
<td>The visual style variant of the clickable icon. Options include: `primary`, `secondary`, and `tertiary`</td>
|
||||
<td>secondary</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Image Input
|
||||
sidebar_position: 6
|
||||
sidebar_custom_props:
|
||||
icon: TbUpload
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import imageInputCode from '!!raw-loader!@site/src/ui/input/components/imageInputCode.js'
|
||||
|
||||
Allows users to upload and remove an image.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/ImageInput']}
|
||||
componentCode={imageInputCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>picture</td>
|
||||
<td>string</td>
|
||||
<td>The image source URL</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onUpload</td>
|
||||
<td>function</td>
|
||||
<td>The function called when a user uploads a new image. It receives the `File` object as a parameter</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onRemove</td>
|
||||
<td>function</td>
|
||||
<td>The function called when the user clicks on the remove button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onAbort</td>
|
||||
<td>function</td>
|
||||
<td>The function called when a user clicks on the abort button during image upload</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>isUploading</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether an image is currently being uploaded</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>errorMessage</td>
|
||||
<td>string</td>
|
||||
<td>An optional error message to display below the image input</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>If `true`, the entire input is disabled, and the buttons are not clickable</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,164 @@
|
||||
---
|
||||
title: Radio
|
||||
sidebar_position: 7
|
||||
sidebar_custom_props:
|
||||
icon: TbCircleDot
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import radioCode from '!!raw-loader!@site/src/ui/input/components/radioCode.js'
|
||||
import radioGroupCode from '!!raw-loader!@site/src/ui/input/components/radioGroupCode.js'
|
||||
|
||||
Used when users may only choose one option from a series of options.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/Radio']}
|
||||
componentCode={radioCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>style</td>
|
||||
<td>`React.CSS` properties</td>
|
||||
<td>Additional inline styles for the component</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional CSS class for additional styling</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>checked</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether the radio button is checked</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>string</td>
|
||||
<td>The label or text associated with the radio button</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The function called when the selected radio button is changed</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onCheckedChange</td>
|
||||
<td>function</td>
|
||||
<td>The function called when the `checked` state of the radio button changes</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>string</td>
|
||||
<td>The size of the radio button. Options include: `large` and `small`</td>
|
||||
<td>small</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>If `true`, the radio button is disabled and not clickable</td>
|
||||
<td>false</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>labelPosition</td>
|
||||
<td>string</td>
|
||||
<td>The position of the label text relative to the radio button. Has two options: `left` and `right`</td>
|
||||
<td>right</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Radio Group
|
||||
|
||||
Groups together related radio buttons.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/RadioGroup', '@/ui/input/components/Radio']}
|
||||
componentCode={radioGroupCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>string</td>
|
||||
<td>The value of the currently selected radio button</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The callback function triggered when the radio button is changed</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onValueChange</td>
|
||||
<td>function</td>
|
||||
<td>The callback function triggered when the selected value in the group changes.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>children</td>
|
||||
<td>`React.ReactNode`</td>
|
||||
<td>Allows you to pass React components (such as Radio) as children to the Radio Group</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Select
|
||||
sidebar_position: 8
|
||||
sidebar_custom_props:
|
||||
icon: TbSelect
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import selectCode from '!!raw-loader!@site/src/ui/input/components/selectCode.js'
|
||||
|
||||
Allows users to pick a value from a list of predefined options.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/Select', '@/ui/display/icon/types/IconComponent']}
|
||||
componentCode={selectCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional CSS class for additional styling</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>When set to `true`, disables user interaction with the component</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>dropdownScopeId</td>
|
||||
<td>string</td>
|
||||
<td>Required prop that uniquely identifies the dropdown scope</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>label</td>
|
||||
<td>string</td>
|
||||
<td>The label to describe the purpose of the `Select` component</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The function called when the selected values change</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>options</td>
|
||||
<td>array</td>
|
||||
<td>Represents the options available for the `Selected` component. It's an array of objects where each object has a `value` (the unique identifier), `label` (the unique identifier), and an optional `Icon`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>string</td>
|
||||
<td>Represents the currently selected value. It should match one of the `value` properties in the `options` array</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,322 @@
|
||||
---
|
||||
title: Text
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbTextSize
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import textInputCode from '!!raw-loader!@site/src/ui/input/components/textInputCode.js'
|
||||
import autosizeTextInputCode from '!!raw-loader!@site/src/ui/input/components/autosizeTextInputCode.js'
|
||||
import entityTitleDoubleTextInputCode from '!!raw-loader!@site/src/ui/input/components/entityTitleDoubleTextInputCode.js'
|
||||
import textAreaCode from '!!raw-loader!@site/src/ui/input/components/textAreaCode.js'
|
||||
|
||||
## Text Input
|
||||
|
||||
Allows users to enter and edit text.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/TextInput']}
|
||||
componentCode={textInputCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>className</td>
|
||||
<td>string</td>
|
||||
<td>Optional name for additional styling</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>label</td>
|
||||
<td>string</td>
|
||||
<td>Represents the label for the input</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The function called when the input value changes</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>fullWidth</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether the input should take up 100% of the width</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>disableHotkeys</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether hotkeys are enabled for the input</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>error</td>
|
||||
<td>string</td>
|
||||
<td>Represents the error message to be displayed. When provided, it also adds an icon error on the right side of the input</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onKeyDown</td>
|
||||
<td>function</td>
|
||||
<td>Called when a key is pressed down while the input field is focused. Receives a `React.KeyboardEvent` as an argument</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>RightIcon</td>
|
||||
<td>IconComponent</td>
|
||||
<td>An optional icon component displayed on the right side of the input</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
The component also accepts other HTML input element props.
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Autosize Text Input
|
||||
|
||||
Text input component that automatically adjusts its height based on the content.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/AutosizeTextInput']}
|
||||
componentCode={autosizeTextInputCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>onValidate</td>
|
||||
<td>function</td>
|
||||
<td>The callback function you want to trigger when the user validates the input</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>minRows</td>
|
||||
<td>number</td>
|
||||
<td>The minimum number of rows for the text area</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>placeholder</td>
|
||||
<td>string</td>
|
||||
<td>The placeholder text you want to display when the text area is empty</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onFocus</td>
|
||||
<td>function</td>
|
||||
<td>The callback function you want to trigger when the text area gains focus</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>variant</td>
|
||||
<td>string</td>
|
||||
<td>The variant of the input. Options include: `default`, `icon`, and `button`</td>
|
||||
<td>default</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>buttonTitle</td>
|
||||
<td>string</td>
|
||||
<td>The title for the button (only applicable for the button variant)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>string</td>
|
||||
<td>The initial value for the text area</td>
|
||||
<td>Empty string</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Entity Title Double Text Input
|
||||
|
||||
Displays a pair of text inputs side by side, allowing the user to edit two related values simultaneously.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/EntityTitleDoubleTextInput']}
|
||||
componentCode={entityTitleDoubleTextInputCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>firstValue</td>
|
||||
<td>string</td>
|
||||
<td>The value for the first text input</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>secondValue</td>
|
||||
<td>string</td>
|
||||
<td>The value for the second text input</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>firstValuePlaceholder</td>
|
||||
<td>string</td>
|
||||
<td>Placeholder text for the first text input, displayed when the input is empty</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>secondValuePlaceholder</td>
|
||||
<td>string</td>
|
||||
<td>Placeholder text for the second text input, displayed when the input is empty</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>The callback function you want to trigger when the text input changes</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Text Area
|
||||
|
||||
Allows you to create multi-line text inputs.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/TextArea']}
|
||||
componentCode={textAreaCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>Indicates whether the text area is disabled</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>minRows</td>
|
||||
<td>number</td>
|
||||
<td>Minimum number of visible rows for the text area. </td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>Callback function triggered when the text area content changes</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>placeholder</td>
|
||||
<td>string</td>
|
||||
<td>Placeholder text displayed when the text area is empty</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>string</td>
|
||||
<td>The current value of the text area</td>
|
||||
<td>Empty string</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Toggle
|
||||
sidebar_position: 10
|
||||
sidebar_custom_props:
|
||||
icon: TbToggleRight
|
||||
---
|
||||
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import { SandpackEditor} from '@site/src/ui/SandpackEditor'
|
||||
import toggleCode from '!!raw-loader!@site/src/ui/input/components/toggleCode.js'
|
||||
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="usage" label="Usage" default>
|
||||
|
||||
<SandpackEditor
|
||||
availableComponentPaths={['@/ui/input/components/Toggle']}
|
||||
componentCode={toggleCode}
|
||||
/>
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="props" label="Props">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Props</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>boolean</td>
|
||||
<td>The current state of the toggle</td>
|
||||
<td>`false`</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>onChange</td>
|
||||
<td>function</td>
|
||||
<td>Callback function triggered when the toggle state changes</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>color</td>
|
||||
<td>string</td>
|
||||
<td>Color of the toggle when it's in the "on" state. If not provided, it uses the theme's blue color</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>toggleSize</td>
|
||||
<td>string</td>
|
||||
<td>Size of the toggle, affecting both height and weight. Has two options: `small` and `medium`</td>
|
||||
<td>medium</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: UI Components
|
||||
sidebar_position: 0
|
||||
displayed_sidebar: uiDocsSidebar
|
||||
sidebar_custom_props:
|
||||
isSidebarRoot: true
|
||||
icon: TbLayoutGrid
|
||||
---
|
||||
@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "Local setup",
|
||||
"position": 1,
|
||||
"collapsible": true,
|
||||
"collapsed": true,
|
||||
"customProps": {
|
||||
"icon": "TbDeviceDesktop"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Docker Setup
|
||||
sidebar_position: 3
|
||||
description: Set up the project with Docker
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandDocker
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
This guide will walk you through provisioning the project with Docker. This comes with the following advantages:
|
||||
- It provides the exact same environment as the core development team.
|
||||
- It includes some extra dependencies (such as `playwright`) that you might need if you wish to contribute to some advanced areas of the project.
|
||||
- It provisions a PostgreSQL database.
|
||||
|
||||
:::info
|
||||
Avoid setting up the project with Docker if you are a Windows (WSL) user, unless you have experience with it, as it will make troubleshooting harder.
|
||||
If you are a Windows user, it's better to use the [yarn installation](/contributor/local-setup/yarn-setup).
|
||||
:::
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Make sure you have the latest [Docker](https://docs.docker.com/get-docker/) and [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) versions installed on your computer.
|
||||
|
||||
You can run `docker --version` to verify the installation.
|
||||
|
||||
## Step 1: Git Clone
|
||||
|
||||
In your terminal, run the following command:
|
||||
|
||||
:::info Note
|
||||
|
||||
It's better to use SSH for this step. If you already haven't set up SSH keys, please do so first. You can learn more about it [here](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
|
||||
|
||||
:::
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="ssh" label="SSH (Recommended)" default>
|
||||
|
||||
```bash
|
||||
git clone git@github.com:twentyhq/twenty.git
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="https" label="HTTPS" >
|
||||
|
||||
```bash
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Step 2: Setup environment variables
|
||||
|
||||
You need to set some environment variables before you can work on the project. Locally, it's better to set them through `.env` files.
|
||||
|
||||
```bash
|
||||
cd twenty
|
||||
cp ./front/.env.example ./front/.env
|
||||
cp ./server/.env.example ./server/.env
|
||||
```
|
||||
|
||||
The default values should work out of the box, except for the postgres URL, which requires a small modification.
|
||||
|
||||
Open `./server/.env` and change to the following:
|
||||
|
||||
```bash
|
||||
PG_DATABASE_URL=postgres://twenty:twenty@postgres:5432/default?connection_limit=1
|
||||
```
|
||||
|
||||
|
||||
## Step 3: Build
|
||||
|
||||
The project includes an environment containerized with Docker and orchestrated with `docker-compose`.
|
||||
This installation method will also provision a PostgreSQL container.
|
||||
|
||||
:::info
|
||||
|
||||
The configuration for the build is in the `infra/dev` folder, but you can run `make` commands directly from the root folder.
|
||||
|
||||
:::
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
## Step 4: Migrate & seed
|
||||
|
||||
Before running the project, you need to initialize the database by running the migrations and seed.
|
||||
|
||||
Start the containers:
|
||||
```bash
|
||||
make up
|
||||
```
|
||||
|
||||
Setup database, run migrations, and seed:
|
||||
```bash
|
||||
make server-database-init
|
||||
```
|
||||
|
||||
## Step 5: Start Twenty
|
||||
|
||||
Run the project with the following commands from the `root` folder:
|
||||
|
||||
```bash
|
||||
make server-start
|
||||
```
|
||||
|
||||
```bash
|
||||
make front-start
|
||||
```
|
||||
|
||||
You should now have:
|
||||
- **Frontend** available on: [http://localhost:3001](http://localhost:3001)
|
||||
- **Server** available on: [http://localhost:3000/graphql](http://localhost:3000/graphql)
|
||||
- **Postgres** available on [http://localhost:5432](http://localhost:5432) and containing database named `default`
|
||||
|
||||
Sign in using a seeded demo account `tim@apple.dev` (password: `Applecar2025`) to start using Twenty.
|
||||
|
||||
### Optional
|
||||
|
||||
If you don't want to use the `make` command and work directly from the container, you can also ssh directly into the container:
|
||||
|
||||
```bash
|
||||
make sh
|
||||
```
|
||||
Then run commands through yarn:
|
||||
```bash
|
||||
cd server
|
||||
yarn database:init
|
||||
```
|
||||
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Docker throws errors while setting up local environment
|
||||
|
||||
If by any chance you run into problems with Docker, you should change the `docker-compose` to `docker compose` in `./infra/dev/Makefile` as `docker-compose` is an old version
|
||||
that's becoming obsolete. (You can find more info [here](https://docs.docker.com/compose/migrate/))
|
||||
@ -0,0 +1,54 @@
|
||||
---
|
||||
title: IDE Setup
|
||||
sidebar_position: 4
|
||||
description: Get the best developer UX with VSCode
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandVscode
|
||||
---
|
||||
|
||||
This section will help you set up your IDE for the project. If you haven't set up your development environment, please refer to the [local setup](/contributor/local-setup) section.
|
||||
|
||||
|
||||
## Visual Studio Code
|
||||
|
||||
You can use any IDE you prefer, but Visual Studio Code is the choice of the core team, and has lots of extensions and settings to share with you.
|
||||
|
||||
|
||||
### Step 1: Installation
|
||||
|
||||
You can download Visual Studio Code from [here](https://code.visualstudio.com/download). Depending on your operating system, you can download the appropriate version.
|
||||
|
||||
### Step 2: Open Project
|
||||
|
||||
Once you install Visual Studio Code, you can open the project by clicking on `File > Open Folder` and selecting `twenty` project root folder.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/contributor/ide-project-open.png" alt="Visual Studio Code: Open Twenty project" width="90%" />
|
||||
</div>
|
||||
|
||||
### Step 3: Extensions
|
||||
|
||||
You can use the recommended extensions for the project. You will find them in `.vscode/extensions.json` file. VSCode should prompt you to install the recommended extensions when you open the project.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/contributor/ide-extensions.png" alt="Visual Studio Code: Install recommended extensions" width="90%" />
|
||||
</div>
|
||||
|
||||
|
||||
### Step 4: (Docker only) Run VSCode in container
|
||||
|
||||
If you are using a [Docker setup](/contributor/local-setup/docker-setup), you will need to run VSCode in the container. You can do that by opening the project, clicking on the `Remote Explorer` icon on the left sidebar and then clicking on `Attach in New window` on `dev-twenty-dev` container.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/contributor/ide-start-dev-container.png" alt="Visual Studio Code: Open in container" width="90%" />
|
||||
</div>
|
||||
|
||||
<br />
|
||||
VSCode will open a new window and you will be able to use it as you would typically do. The only difference is that you will be running VSCode inside the container and you will have access to all the tools and dependencies installed in the container.
|
||||
|
||||
<br /><br />
|
||||
If you stop your containers, you will need to restart them before opening the project in VSCode again.
|
||||
|
||||
## You are all set
|
||||
|
||||
You are all set to start contributing to the project. If you have any questions, feel free to reach out to the team on [Discord](https://twenty.com/discord).
|
||||
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Local Setup
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbDeviceDesktop
|
||||
---
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
|
||||
|
||||
|
||||
Twenty aims for developer-friendliness, and your local installation should be up and running in just a bit.
|
||||
|
||||
<DocCardList/>
|
||||
|
||||
## Discord
|
||||
|
||||
If you have any questions or need help, you can join Twenty's [Discord](https://twenty.com/discord) server.
|
||||
|
||||
## MacOS and Linux users
|
||||
|
||||
It's better to use [yarn installation](/contributor/local-setup/yarn-setup) as this is the easiest way to get started.
|
||||
But there's also an easy way to run the project with [Docker](/contributor/local-setup/docker-setup) that you can use if you are familiar with containerized environments.
|
||||
|
||||
## Windows users
|
||||
|
||||
Windows users can install the project through WSL2. [This guide](/contributor/local-setup/yarn-setup) can help you get started.
|
||||
|
||||
## Project structure
|
||||
|
||||
The repository has the following structure:
|
||||
```
|
||||
twenty
|
||||
└───docs // contains this documentation
|
||||
└───front // contains the frontend code for the application
|
||||
└───server // contains the backend code for the application
|
||||
└───infra // contains docker configurations for development and production deployments
|
||||
```
|
||||
|
||||
## IDE Setup
|
||||
|
||||
Once Twenty is running on your computer, you will get the best experience by using an IDE that supports TypeScript and ESLint.
|
||||
You will find a guide for [VSCode](/contributor/local-setup/ide-setup) further in the documentation.
|
||||
___
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
sidebar_position: 5
|
||||
description: Common problems & their solutions.
|
||||
sidebar_custom_props:
|
||||
icon: TbExclamationCircle
|
||||
---
|
||||
|
||||
|
||||
## CR line breaks found [Windows]
|
||||
|
||||
This is due to the line break characters of Windows and the git configuration. Try running:
|
||||
```
|
||||
git config --global core.autocrlf false
|
||||
```
|
||||
|
||||
Then delete the repository and clone it again.
|
||||
|
||||
## Extra yarn files
|
||||
|
||||
If you have extra files created by yarn (`yarn.lock`, `.yarnrc.yml`, `.yarn`), you may have a yarn version issue.
|
||||
Try installing [yarn classic](https://classic.yarnpkg.com/lang/en/)!
|
||||
|
||||
## Missing metadata schema
|
||||
|
||||
During Twenty installation, you need to provision your postgres database with the right schemas, extensions, and users.
|
||||
This documentation includes [different ways](/contributor/local-setup/yarn-setup#step-2-set-up-postgresql-database) to set up your postgres instance.
|
||||
|
||||
If you're successful in running this provisioning, you should have `default` and `metadata` schemas in your database.
|
||||
If you don't, make sure you don't have more than one postgres instance running on your computer.
|
||||
216
packages/twenty-docs/docs/contributor/local-setup/yarn-setup.mdx
Normal file
216
packages/twenty-docs/docs/contributor/local-setup/yarn-setup.mdx
Normal file
@ -0,0 +1,216 @@
|
||||
---
|
||||
title: Yarn Setup
|
||||
sidebar_position: 1
|
||||
description: |
|
||||
Set up the project with Yarn
|
||||
sidebar_custom_props:
|
||||
icon: TbScript
|
||||
---
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
In this document, you'll learn how to install the project using yarn. You should use this method since it's the easiest way to get started but you can also run the project with [Docker](/contributor/local-setup/docker-setup).
|
||||
|
||||
:::info
|
||||
`npm` does not support local packages well, opt for `yarn` instead.
|
||||
:::
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="yarn" label="Linux and MacOS" default>
|
||||
|
||||
Before you can install and use Twenty, make sure you install the following on your computer:
|
||||
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
- [Node v18](https://nodejs.org/en/download)
|
||||
- [yarn v1](https://classic.yarnpkg.com/lang/en/docs/install/)
|
||||
- [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="wsl" label="Windows (WSL)" >
|
||||
|
||||
1. Install WSL
|
||||
Open PowerShell as Administrator and run:
|
||||
|
||||
```powershell
|
||||
wsl --install
|
||||
```
|
||||
You should now see a prompt to restart your computer. If not, restart it manually.
|
||||
|
||||
Upon restart, a powershell window will open and install Ubuntu. This may take up some time.
|
||||
You'll see a prompt to create a username and password for your Ubuntu installation.
|
||||
|
||||
2. Install and configure git
|
||||
|
||||
```bash
|
||||
sudo apt-get install git
|
||||
git config --global user.name "Your Name"
|
||||
git config --global user.email "youremail@domain.com"
|
||||
```
|
||||
|
||||
3. Install Node.js, nvm, yarn
|
||||
```bash
|
||||
sudo apt-get install curl
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||
curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
```
|
||||
Close and reopen your terminal to start using nvm.
|
||||
|
||||
:::caution Note
|
||||
|
||||
Avoid using Docker on WSL as it adds an extra layer of complexity.
|
||||
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Git Clone
|
||||
|
||||
In your terminal, run the following command.
|
||||
|
||||
:::info Note
|
||||
|
||||
It's better to use SSH for this step. If you already haven't set up SSH keys, please do so first. You can learn more about it [here](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
|
||||
|
||||
:::
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="ssh" label="SSH (Recommended)" default>
|
||||
|
||||
```bash
|
||||
git clone git@github.com:twentyhq/twenty.git
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="https" label="HTTPS" >
|
||||
|
||||
```bash
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Step 2: Set up PostgreSQL Database
|
||||
You need to have a PostgreSQL instance available to be able to use Twenty.
|
||||
You need to provision this database with a `twenty` user (password: `twenty`), a `default` database and a `test` database.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="linux" label="Linux" default>
|
||||
<b>Option 1:</b> To provision your database locally:
|
||||
<br /><br />
|
||||
|
||||
```bash
|
||||
cd twenty
|
||||
make provision-postgres-linux
|
||||
```
|
||||
|
||||
<b>Option 2:</b> If you have docker installed:
|
||||
<br /><br />
|
||||
|
||||
```bash
|
||||
cd twenty
|
||||
make provision-postgres-docker
|
||||
```
|
||||
This will create a Docker container, exposing a PostgresSQL instance at [http://localhost:5432](http://localhost:5432).
|
||||
You can access this using `twenty` postgres user (password: `twenty`)
|
||||
</TabItem>
|
||||
<TabItem value="mac-os" label="Mac OS" default>
|
||||
|
||||
<b>Option 1:</b> To provision your database locally:
|
||||
<br /><br />
|
||||
|
||||
```bash
|
||||
cd twenty
|
||||
make provision-postgres-macos-intel #for intel architecture
|
||||
make provision-postgres-macos-arm #for M1/M2/M3 architecture
|
||||
```
|
||||
|
||||
<b>Option 2:</b> If you have docker installed:
|
||||
<br /><br />
|
||||
|
||||
```bash
|
||||
cd twenty
|
||||
make provision-postgres-docker
|
||||
```
|
||||
This will create a Docker container, exposing a PostgresSQL instance at [http://localhost:5432](http://localhost:5432).
|
||||
You can access this using `twenty` postgres user (password: `twenty`)
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="wsl" label="Windows (WSL)">
|
||||
|
||||
It's better to provision your database locally:
|
||||
```bash
|
||||
cd twenty
|
||||
make provision-postgres-linux
|
||||
```
|
||||
This will create a Docker container, exposing a PostgresSQL instance at [http://localhost:5432](http://localhost:5432).
|
||||
You can access this using `twenty` postgres user (password: `twenty`)
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Step 3: Setup environment variables
|
||||
|
||||
Twenty requires you to set some environment variables. Locally, you should set them through a `.env` file.
|
||||
|
||||
To do so, make copies of the `.env.example` files in `/front` and `/server`:
|
||||
```bash
|
||||
cp ./front/.env.example ./front/.env
|
||||
cp ./server/.env.example ./server/.env
|
||||
```
|
||||
|
||||
## Step 4: Server setup
|
||||
|
||||
:::info
|
||||
|
||||
Use `nvm` to install the correct `node` version. The `server/.nvmrc` ensures all contributors use the same version.
|
||||
|
||||
:::
|
||||
|
||||
To build Twenty server and seed some data into your database, run the following commands:
|
||||
```bash
|
||||
cd server
|
||||
nvm install #recommended
|
||||
nvm use #recommended
|
||||
yarn
|
||||
yarn database:init
|
||||
yarn start:dev
|
||||
```
|
||||
|
||||
Twenty's server will be up and running at [http://localhost:3000/graphql](http://localhost:3000/graphql).
|
||||
|
||||
## Step 5: Frontend setup
|
||||
|
||||
:::info
|
||||
|
||||
For the frontend setup, too, it's better to use `nvm` to install the right node version.
|
||||
|
||||
:::
|
||||
|
||||
To set up the frontend, run the following commands in another terminal:
|
||||
|
||||
```bash
|
||||
cd front
|
||||
nvm install #recommended
|
||||
nvm use #recommended
|
||||
yarn
|
||||
yarn start
|
||||
```
|
||||
|
||||
Twenty's frontend will be running at [http://localhost:3001](http://localhost:3001). Just login using the seeded demo account: `tim@apple.dev` to start using Twenty.
|
||||
|
||||
# Troubleshouting
|
||||
|
||||
## Error: Failed to execute init.sql script.
|
||||
|
||||
```bash
|
||||
cp: ./infra/dev/postgres/init.sql: No such file or directory
|
||||
psql: error: /tmp/init.sql: No such file or directory
|
||||
```
|
||||
|
||||
If you get this error it means you are not launching the local database init script from the repo's root folder. Make sure you're in `twenty/` and not in `twenty/infra/dev/scripts`.
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"position": 4
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Basics",
|
||||
"position": 1
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Custom Objects
|
||||
sidebar_position: 4
|
||||
sidebar_custom_props:
|
||||
icon: TbAugmentedReality
|
||||
---
|
||||
|
||||
Objects are structures that allow you to store data (records, attributes, and values) specific to an organization. Twenty provides both standard and custom objects.
|
||||
|
||||
Standard objects are in-built objects with a set of attributes available for all users. Examples of standard objects in Twenty include Company and Person. Standard objects have standard fields that are also available for all Twenty users, like Company.displayName.
|
||||
|
||||
Custom objects are objects that you can create to store information that is unique to your organization. They are not built-in; members of your workspace can create and customize custom objects to hold information that standard objects aren't suitable for.
|
||||
|
||||
|
||||
## High-level schema
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/contributor/custom-object-schema.jpeg" alt="High level schema" />
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
## How it works
|
||||
|
||||
Custom objects come from metadata tables that determine the shape, name, and type of the objects. All this information is present in the metadata schema database, consisting of tables:
|
||||
|
||||
- **DataSource**: Details where the data is present.
|
||||
- **Object**: Describes the object and links to a DataSource.
|
||||
- **Field**: Outlines an Object's fields and connects to the Object.
|
||||
|
||||
To add a custom object, the workspaceMember will query the /metadata API. This updates the metadata accordingly and computes a GraphQL schema based on the metadata, storing it in a GQL cache for later use.
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/contributor/add-custom-objects.jpeg" alt="Query the /metadata API to add custom objects" />
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
To fetch data, the process involves making queries through the /graphql endpoint and passing them through the Query Resolver.
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/contributor/custom-objects-schema.png" alt="Query the /graphql endpoint to fetch data" />
|
||||
</div>
|
||||
@ -0,0 +1,132 @@
|
||||
---
|
||||
title: Folder Architecture
|
||||
sidebar_position: 2
|
||||
description: A detailed look into our server folder architecture
|
||||
sidebar_custom_props:
|
||||
icon: TbFolder
|
||||
---
|
||||
|
||||
|
||||
The backend directory structure is as follows:
|
||||
|
||||
```
|
||||
server
|
||||
└───ability
|
||||
└───constants
|
||||
└───core
|
||||
└───database
|
||||
└───decorators
|
||||
└───filters
|
||||
└───guards
|
||||
└───health
|
||||
└───integrations
|
||||
└───metadata
|
||||
└───workspace
|
||||
└───utils
|
||||
```
|
||||
|
||||
## Ability
|
||||
|
||||
Defines permissions and includes handlers for each entity.
|
||||
|
||||
## Decorators
|
||||
|
||||
Defines custom decorators in NestJS for added functionality.
|
||||
|
||||
See [custom decorators](https://docs.nestjs.com/custom-decorators) for more details.
|
||||
|
||||
## Filters
|
||||
|
||||
Includes exception filters to handle exceptions that might occur in GraphQL endpoints.
|
||||
|
||||
## Guards
|
||||
|
||||
See [guards](https://docs.nestjs.com/guards) for more details.
|
||||
|
||||
## Health
|
||||
|
||||
Includes a publicly available REST API (healthz) that returns a JSON to confirm whether the database is working as expected.
|
||||
|
||||
## Metadata
|
||||
|
||||
Defines custom objects and makes available a GraphQL API (graphql/metadata).
|
||||
|
||||
## Workspace
|
||||
|
||||
Generates and serves custom GraphQL schema based on the metadata.
|
||||
|
||||
### Workspace Directory Structure
|
||||
|
||||
```
|
||||
workspace
|
||||
└───workspace-schema-builder
|
||||
└───factories
|
||||
└───graphql-types
|
||||
└───database
|
||||
└───interfaces
|
||||
└───object-definitions
|
||||
└───services
|
||||
└───storage
|
||||
└───utils
|
||||
└───workspace-resolver-builder
|
||||
└───factories
|
||||
└───interfaces
|
||||
└───workspace-query-builder
|
||||
└───factories
|
||||
└───interfaces
|
||||
└───workspace-query-runner
|
||||
└───interfaces
|
||||
└───utils
|
||||
└───workspace-datasource
|
||||
└───workspace-manager
|
||||
└───workspace-migration-runner
|
||||
└───utils
|
||||
└───workspace.module.ts
|
||||
└───workspace.factory.spec.ts
|
||||
└───workspace.factory.ts
|
||||
```
|
||||
|
||||
|
||||
The root of the workspace directory includes the `workspace.factory.ts`, a file containing the `createGraphQLSchema` function. This function generates workspace-specific schema by using the metadata to tailor a schema for individual workspaces. By separating the schema and resolver construction, we use the `makeExecutableSchema` function, which combines these discrete elements.
|
||||
|
||||
This strategy is not just about organization, but also helps with optimization, such as caching generated type definitions to enhance performance and scalability.
|
||||
|
||||
### Workspace Schema builder
|
||||
|
||||
Generates the GraphQL schema, and includes:
|
||||
|
||||
#### Factories:
|
||||
|
||||
Specialised constructors to generate GraphQL-related constructs.
|
||||
- The type.factory translates field metadata into GraphQL types using `TypeMapperService`.
|
||||
- The type-definition.factory creates GraphQL input or output objects derived from `objectMetadata`.
|
||||
|
||||
#### GraphQL Types
|
||||
|
||||
Includes enumerations, inputs, objects, and scalars, and serves as the building blocks for the schema construction.
|
||||
|
||||
#### Interfaces and Object Definitions
|
||||
|
||||
Contains the blueprints for GraphQL entities, and includes both predefined and custom types like `MONEY` or `URL`.
|
||||
|
||||
#### Services
|
||||
|
||||
Contains the service responsible for associating FieldMetadataType with its appropriate GraphQL scalar or query modifiers.
|
||||
|
||||
#### Storage
|
||||
|
||||
Includes the `TypeDefinitionsStorage` class that contains reusable type definitions, preventing duplication of GraphQL types.
|
||||
|
||||
### Workspace Resolver Builder
|
||||
|
||||
Creates resolver functions for querying and mutatating the GraphQL schema.
|
||||
|
||||
Each factory in this directory is responsible for producing a distinct resolver type, such as the `FindManyResolverFactory`, designed for adaptable application across various tables.
|
||||
|
||||
### Workspace Query Builder
|
||||
|
||||
Includes factories that generate `pg_graphql` queries.
|
||||
|
||||
### Workspace Query Runner
|
||||
|
||||
Runs the generated queries on the database and parses the result.
|
||||
@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Overview
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbEyeglass
|
||||
---
|
||||
|
||||
Twenty primarily uses NestJS for the backend.
|
||||
|
||||
Prisma was the first choice as the ORM with a lot of auto-generated code under the hood. But to offer users flexibility and allow them to create custom fields and custom objects, something more low-level than Prisma made more sense to have more fine-grained control. This is why the project now uses TypeORM.
|
||||
|
||||
Here's what the tech stack now looks like.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
**Core**
|
||||
- [NestJS](https://nestjs.com/)
|
||||
- [TypeORM](https://typeorm.io/)
|
||||
- [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
|
||||
|
||||
**Database**
|
||||
- [Postgres](https://www.postgresql.org/)
|
||||
|
||||
**Third-party integrations**
|
||||
- [Sentry](https://sentry.io/welcome/) for tracking bugs
|
||||
|
||||
**Testing**
|
||||
- [Jest](https://jestjs.io/)
|
||||
|
||||
**Tooling**
|
||||
- [Yarn](https://yarnpkg.com/)
|
||||
- [ESLint](https://eslint.org/)
|
||||
|
||||
**Development**
|
||||
- [AWS EKS](https://aws.amazon.com/eks/)
|
||||
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: Message Queue
|
||||
sidebar_position: 5
|
||||
sidebar_custom_props:
|
||||
icon: TbSchema
|
||||
---
|
||||
|
||||
Queues facilitate async operations to be performed. They can be used for performing background tasks such as sending a welcome email on register.
|
||||
Each use case will have its own queue class extended from `MessageQueueServiceBase`.
|
||||
|
||||
Currently, queue supports two drivers which can be configurred by env variable `MESSAGE_QUEUE_TYPE`.
|
||||
1. `pg-boss`: this is the default driver, which uses [pg-boss](https://github.com/timgit/pg-boss) under the hood.
|
||||
2. `bull-mq`: this uses [bull-mq](https://bullmq.io/) under the hood.
|
||||
|
||||
## Steps to create and use a new queue
|
||||
|
||||
1. Add a queue name for your new queue under enum `MESSAGE_QUEUES`.
|
||||
2. Provide the factory implementation of the queue with the queue name as the dependency token.
|
||||
3. Inject the queue that you created in the required module/service with the queue name as the dependency token.
|
||||
4. Add worker class with token based injection just like producer.
|
||||
|
||||
### Example usage
|
||||
```ts
|
||||
class Resolver {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
|
||||
|
||||
async onSomeAction() {
|
||||
//business logic
|
||||
await this.queue.add(someData);
|
||||
}
|
||||
}
|
||||
|
||||
//async worker
|
||||
class CustomWorker {
|
||||
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
|
||||
this.initWorker();
|
||||
}
|
||||
|
||||
async initWorker() {
|
||||
await this.queue.work(async ({ id, data }) => {
|
||||
//worker logic
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Development workflow
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbTopologyStar
|
||||
---
|
||||
|
||||
## First time setup
|
||||
|
||||
```
|
||||
cd server
|
||||
yarn # install dependencies
|
||||
|
||||
yarn prisma:migrate # run migrations
|
||||
yarn prisma:generate # generate prisma and nestjs-graphql schemas
|
||||
yarn prisma:seed # provision database with seeds
|
||||
|
||||
# alternatively, you can run
|
||||
yarn prisma:reset # all-in-one command to reset, migrate, seed and generate schemas
|
||||
```
|
||||
|
||||
## Starting a new feature
|
||||
|
||||
Make sure your database is running on the URL provided in your `server/.env` file.
|
||||
|
||||
```
|
||||
cd server
|
||||
yarn
|
||||
yarn prisma:migrate && yarn prisma:generate
|
||||
|
||||
yarn start:dev
|
||||
```
|
||||
|
||||
## Lint
|
||||
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
## Resetting the database
|
||||
|
||||
If you want to reset the database, you can run the following command:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
yarn database:reset
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
This will drop the database and re-run the migrations and seed.
|
||||
|
||||
Make sure to back up any data you want to keep before running this command.
|
||||
|
||||
:::
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Others",
|
||||
"position": 2
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Best Practices
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbChecklist
|
||||
---
|
||||
|
||||
This document outlines the best practices you should follow when working on the backend.
|
||||
|
||||
## Follow a modular approach
|
||||
|
||||
The backend follows a modular approach, which is a fundamental principle when working with NestJS. Make sure you break down your code into reusable modules to maintain a clean and organized codebase.
|
||||
Each module should encapsulate a particular feature or functionality and have a well-defined scope. This modular approach enables clear separation of concerns and removes unnecessary complexities.
|
||||
|
||||
## Expose services to use in modules
|
||||
|
||||
Always create services that have a clear and single responsibility, which enhances code readability and maintainability. Name the services descriptively and consistently.
|
||||
|
||||
You should also expose services that you want to use in other modules. Exposing services to other modules is possible through NestJS's powerful dependency injection system, and promotes loose coupling between components.
|
||||
|
||||
## Avoid using `any` type
|
||||
|
||||
When you declare a variable as `any`, TypeScript's type checker doesn't perform any type checking, making it possible to assign any type of values to the variable. TypeScript uses type inference to determine the type of variable based on the value. By declaring it as `any`, TypeScript can no longer infer the type. This makes it hard to catch type-related errors during development, leading to runtime errors and makes the code less maintainable, less reliable, and harder to understand for others.
|
||||
|
||||
This is why everything should have a type. So if you create a new object with a first name and last name, you should create an interface or type that contains a first name and last name that defines the shape of the object you are manipulating.
|
||||
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Zapier App
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandZapier
|
||||
---
|
||||
|
||||
Effortlessly sync Twenty with 3000+ apps using [Zapier](https://zapier.com/). Automate tasks, boost productivity, and supercharge your customer relationships!
|
||||
|
||||
## About Zapier
|
||||
|
||||
Zapier is a tool that allows you automate workflows by connecting the apps that your team uses everyday. The fundamental concept of Zapier is automation workflows, called Zaps, and include triggers and actions.
|
||||
|
||||
You can learn more about how Zapier works [here](https://zapier.com/how-it-works).
|
||||
|
||||
## Setup
|
||||
|
||||
### Step 1: Install Zapier packages
|
||||
|
||||
```bash
|
||||
cd packages/twenty-zapier
|
||||
yarn
|
||||
```
|
||||
|
||||
### Step 2: Login with the CLI
|
||||
|
||||
Use your Zapier credentials to log in using the CLI:
|
||||
|
||||
```bash
|
||||
zapier login
|
||||
```
|
||||
|
||||
### Step 3: Set environment variables
|
||||
|
||||
From the `packages/twenty-zapier` folder, run:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
Run the application locally, go to [http://localhost:3000/settings/developers/api-keys](http://localhost:3000/settings/developers/api-keys), and generate an API key.
|
||||
|
||||
Replace the **YOUR_API_KEY** value in the `.env` file with the API key you just generated.
|
||||
|
||||
## Development
|
||||
|
||||
:::caution Note
|
||||
|
||||
Make sure to run `yarn build` before any `zapier` command.
|
||||
|
||||
:::
|
||||
|
||||
### Test
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
### Lint
|
||||
```bash
|
||||
yarn format
|
||||
```
|
||||
### Watch and compile as you edit code
|
||||
```bash
|
||||
yarn watch
|
||||
```
|
||||
### Validate your Zapier app
|
||||
```bash
|
||||
yarn validate
|
||||
```
|
||||
### Deploy your Zapier app
|
||||
```bash
|
||||
yarn deploy
|
||||
```
|
||||
### List all Zapier CLI commands
|
||||
```bash
|
||||
zapier
|
||||
```
|
||||
12
packages/twenty-docs/docs/contributor/server/server.mdx
Normal file
12
packages/twenty-docs/docs/contributor/server/server.mdx
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Backend Development
|
||||
displayed_sidebar: backendSidebar
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbTerminal
|
||||
isSidebarRoot: true
|
||||
---
|
||||
|
||||
Welcome to the Backend Development section of the documentation.
|
||||
|
||||
Here you will find information about the development process, the recommended tools, and the best practices you should follow.
|
||||
4
packages/twenty-docs/docs/developer/_category_.json
Normal file
4
packages/twenty-docs/docs/developer/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Developer guide",
|
||||
"position": 3
|
||||
}
|
||||
25
packages/twenty-docs/docs/developer/graphql_api.mdx
Normal file
25
packages/twenty-docs/docs/developer/graphql_api.mdx
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: GraphQL API
|
||||
sidebar_position: 2
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandGraphql
|
||||
---
|
||||
|
||||
Use the [in-browser GraphiQL app](https://docs.twenty.com/graphql/) to browse, query, and mutate the introspection query.
|
||||
|
||||
## What is GraphQL?
|
||||
|
||||
GraphQL is a query language for APIs that enables declarative data fetching that allows a client to specify the exact data it needs from the API.
|
||||
|
||||
Instead of exposing various endpoints that return fixed data structures, GraphQL exposes only a single endpoint that precisely returns the data that the client asked for. This makes GraphQL more flexible and efficient than other kinds of APIs, like REST APIs.
|
||||
|
||||
You can learn more about GraphQL by going through this [Introduction](https://www.howtographql.com/basics/0-introduction/).
|
||||
|
||||
## About GraphQL Introspection
|
||||
|
||||
GraphQL query language is strongly typed, which makes it possible for you to query and understand the underlying schema.
|
||||
|
||||
With the Introspection feature, you can query the schema and discover the queries (to request data), mutations (to update data), types, and fields available in a particular GraphQL API.
|
||||
|
||||
## Try the GraphQL Playground
|
||||
You can use the browser-based, interactive [GraphQL playground](https://docs.twenty.com/graphql/) to run mutations and queries to discover valid fields and where you can use them.
|
||||
9
packages/twenty-docs/docs/developer/rest_api.mdx
Normal file
9
packages/twenty-docs/docs/developer/rest_api.mdx
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Rest API
|
||||
sidebar_position: 3
|
||||
sidebar_class_name: coming-soon
|
||||
sidebar_custom_props:
|
||||
icon: TbApi
|
||||
---
|
||||
|
||||
Coming soon!
|
||||
38
packages/twenty-docs/docs/index.mdx
Normal file
38
packages/twenty-docs/docs/index.mdx
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
id: homepage
|
||||
sidebar_position: 0
|
||||
sidebar_class_name: display-none
|
||||
title: Welcome
|
||||
custom_edit_url: null
|
||||
---
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import Footer from '@theme/Footer'
|
||||
|
||||
Twenty is an Open Source CRM that provides flexibility, tailored to your business needs. It helps you break free from vendor lock-in and limitations, and provides the tools you need to harness the full potential of your data while ensuring a sleek and effortlessly intuitive design that teams will love to use.
|
||||
<ThemedImage sources={{light: "../img/light-doc-preview.png", dark:"../img/dark-doc-preview.png"}} style={{width:'100%', maxWidth:'800px'}}/>
|
||||
|
||||
|
||||
## Idea Behind Twenty
|
||||
We’ve spent thousands of hours grappling with traditional CRMs like Pipedrive and Salesforce to align them with our business needs, only to end up frustrated—customizations are complex and the closed ecosystems of these platforms can feel restrictive.
|
||||
|
||||
The need for a CRM solution that empowers rather than constrains was clear, which inspired us to create Twenty. It's a next-generation open-source CRM that offers you the flexibility to shape it according to your business objectives and meet your team’s unique needs. Twenty is full of powerful features to give you full control and help you win more deals.
|
||||
|
||||
## Getting started
|
||||
|
||||
There are three ways for you to get started with Twenty:
|
||||
- **Cloud:** The fastest and easiest way to try the app (it's free)
|
||||
- **Local:** If you're a developer and would like to experiment or contribute to the app
|
||||
- **Self-hosting:** If you want greater control over your data and want to run the app on your own server
|
||||
|
||||
See the [Getting Started](./start/getting-started/) guide to learn more.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are what makes the open source community such a great place.
|
||||
|
||||
Code contributions through pull request are most welcome. See the [local setup guide](../contributor/local-setup) to get started.
|
||||
|
||||
You can also contribute by creating an issue to report a bug you've spotted, joining discussions or writing documentation.
|
||||
|
||||
|
||||
<Footer/>
|
||||
4
packages/twenty-docs/docs/start/_category_.json
Normal file
4
packages/twenty-docs/docs/start/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Start",
|
||||
"position": 1
|
||||
}
|
||||
15
packages/twenty-docs/docs/start/bug-and-requests.mdx
Normal file
15
packages/twenty-docs/docs/start/bug-and-requests.mdx
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Bugs & Requests
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbBug
|
||||
---
|
||||
|
||||
## Reporting Bugs
|
||||
To report a bug, please [create an issue on GitHub](https://github.com/twentyhq/twenty/issues/new).
|
||||
|
||||
You can also ask for help on [Discord](https://twenty.com/discord).
|
||||
|
||||
## Feature Requests
|
||||
|
||||
If you're not sure it's a bug and you feel it's closer to a feature request, then you should probably [open a discussion instead](https://github.com/twentyhq/twenty/discussions/new).
|
||||
29
packages/twenty-docs/docs/start/getting-started.mdx
Normal file
29
packages/twenty-docs/docs/start/getting-started.mdx
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Getting Started
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbRocket
|
||||
---
|
||||
import ThemedImage from '@theme/ThemedImage';
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
There are three ways for you to get started with Twenty:
|
||||
### 1. Cloud
|
||||
|
||||
The easiest way to try the app is to sign up on [app.twenty.com](https://app.twenty.com).
|
||||
|
||||
The sign-up is free.
|
||||
|
||||
<ThemedImage sources={{light: "/img/light-sign-in.png", dark:"/img/dark-sign-in.png"}} style={{width:'100%', maxWidth:'800px'}}/>
|
||||
|
||||
### 2. Local
|
||||
If you're a developer and would like to experiment or contribute to the app, you can install Twenty on your local environment. Follow the [local setup](/contributor/local-setup) guide to get started.
|
||||
|
||||
### 3. Self-hosting
|
||||
You can also find [self-hosting options](/start/self-hosting) if you want greater control over your data and want to run the app on your own server. Right now, Docker containers are the only hosting option available, with more simple options to self-host Twenty coming soon.
|
||||
|
||||
|
||||
___
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"position": 2,
|
||||
"collapsible": true,
|
||||
"collapsed": true
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
---
|
||||
title: Enviroment Variables
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbVariable
|
||||
---
|
||||
|
||||
import OptionTable from '@site/src/theme/OptionTable'
|
||||
|
||||
## Frontend
|
||||
|
||||
<OptionTable options={[
|
||||
['REACT_APP_SERVER_BASE_URL', 'http://localhost:3000', 'Url of backend server'],
|
||||
['REACT_APP_SERVER_AUTH_URL', 'http://localhost:3000/auth', 'Auth Url of backend server'],
|
||||
['REACT_APP_SERVER_FILES_URL', 'http://localhost:3000/files', 'Files Url of backend server'],
|
||||
['GENERATE_SOURCEMAP', 'false', 'Generate source maps for debugging'],
|
||||
['CHROMATIC_PROJECT_TOKEN', '', 'Chromatic token used for CI'],
|
||||
]}></OptionTable>
|
||||
|
||||
|
||||
## Backend
|
||||
|
||||
### Config
|
||||
|
||||
<OptionTable options={[
|
||||
['PG_DATABASE_URL', 'postgres://user:pw@localhost:5432/default?connection_limit=1', 'Database connection'],
|
||||
['REDIS_HOST', '127.0.0.1', 'Redis connection host'],
|
||||
['REDIS_PORT', '6379', 'Redis connection port'],
|
||||
['FRONT_BASE_URL', 'http://localhost:3001', 'Url to the hosted frontend'],
|
||||
['SERVER_URL', 'http://localhost:3000', 'Url to the hosted server'],
|
||||
['PORT', '3000', 'Port'],
|
||||
]}></OptionTable>
|
||||
|
||||
### Tokens
|
||||
|
||||
<OptionTable options={[
|
||||
['ACCESS_TOKEN_SECRET', '<random>', 'Secret used for the access tokens'],
|
||||
['ACCESS_TOKEN_EXPIRES_IN', '30m', 'Access token expiration time'],
|
||||
['LOGIN_TOKEN_SECRET', '<random>', 'Secret used for the login tokens'],
|
||||
['LOGIN_TOKEN_EXPIRES_IN', '15m', 'Login token expiration time'],
|
||||
['REFRESH_TOKEN_SECRET', '<random>', 'Secret used for the refresh tokens'],
|
||||
['REFRESH_TOKEN_EXPIRES_IN', '90d', 'Refresh token expiration time'],
|
||||
['REFRESH_TOKEN_COOL_DOWN', '1m', 'Refresh token cooldown'],
|
||||
['API_TOKEN_EXPIRES_IN', '1000y', 'Api token expiration time'],
|
||||
]}></OptionTable>
|
||||
|
||||
### Auth
|
||||
|
||||
<OptionTable options={[
|
||||
['MESSAGING_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Gmail API connection'],
|
||||
['MESSAGING_PROVIDER_GMAIL_CALLBACK_URL', '', 'Gmail auth callback'],
|
||||
['AUTH_GOOGLE_ENABLED', 'false', 'Enable Goole SSO login'],
|
||||
['AUTH_GOOGLE_CLIENT_ID', '', 'Google client ID'],
|
||||
['AUTH_GOOGLE_CLIENT_SECRET', '', 'Google client secret'],
|
||||
['AUTH_GOOGLE_CALLBACK_URL', '', 'Google auth callback'],
|
||||
['FRONT_AUTH_CALLBACK_URL', 'http://localhost:3001/verify ', 'Callback used for Login page'],
|
||||
]}></OptionTable>
|
||||
|
||||
### Storage
|
||||
|
||||
<OptionTable options={[
|
||||
['STORAGE_TYPE', 'local', "Storage driver: 'local' or 's3'"],
|
||||
['STORAGE_S3_REGION', '', 'Storage Region'],
|
||||
['STORAGE_S3_NAME', '', 'Bucket Name'],
|
||||
['STORAGE_S3_ENDPOINT', '', 'Use if a different Endpoint is needed (for example Google)'],
|
||||
['STORAGE_LOCAL_PATH', '.local-storage', 'data path (local storage)'],
|
||||
]}></OptionTable>
|
||||
|
||||
### Message Queue
|
||||
|
||||
<OptionTable options={[
|
||||
['MESSAGE_QUEUE_TYPE', 'pg-boss', "Queue driver: 'pg-boss' or 'bull-mq'"],
|
||||
]}></OptionTable>
|
||||
|
||||
### Logging
|
||||
|
||||
<OptionTable options={[
|
||||
['LOGGER_DRIVER', 'console', "The logging driver can be: 'console' or 'sentry'"],
|
||||
['LOG_LEVEL', 'error,warn', "The loglevels which are logged to the logging driver. Can include: 'log', 'warn', 'error'"],
|
||||
['SENTRY_DSN', 'https://xxx@xxx.ingest.sentry.io/xxx', 'The sentry logging endpoint used if sentry logging driver is selected'],
|
||||
]}></OptionTable>
|
||||
|
||||
### Support
|
||||
|
||||
|
||||
<OptionTable options={[
|
||||
['SUPPORT_DRIVER', 'front', "Support driver ('front' or 'none')"],
|
||||
['SUPPORT_FRONT_HMAC_KEY', '<secret>', 'Suport chat key'],
|
||||
['SUPPORT_FRONT_CHAT_ID', '<id>', 'Support chat id'],
|
||||
]}></OptionTable>
|
||||
|
||||
### Telemetry
|
||||
<OptionTable options={[
|
||||
['TELEMETRY_ENABLED', 'true', 'Change this if you want to disable telemetry'],
|
||||
['TELEMETRY_ANONYMIZATION_ENABLED', 'true', 'Telemetry is anonymized by default, you probably don\'t want to change this'],
|
||||
]}></OptionTable>
|
||||
|
||||
### Debug / Development
|
||||
|
||||
<OptionTable options={[
|
||||
['DEBUG_MODE', 'true', 'Activate debug mode'],
|
||||
['SIGN_IN_PREFILLED', 'true', 'Prefill the Signin form for usage in a demo or dev enviroment'],
|
||||
]}></OptionTable>
|
||||
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Self-Hosting
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbServer
|
||||
---
|
||||
Right now, Docker containers are the only hosting option available, with more simple options to self-host Twenty coming soon.
|
||||
Feel free to open issues on [GitHub](https://github.com/twentyhq/twenty/issues/new) if you want support for a specific cloud provider.
|
||||
|
||||
## Production docker containers
|
||||
|
||||
Prebuilt images for both front and back-end can be found on [docker hub](https://hub.docker.com/r/twentycrm/).
|
||||
|
||||
For correct operation your will need to set [environment variables](enviroment-variables), a example configuration can be found [here](https://github.com/twentyhq/twenty/blob/main/server/.env.example).
|
||||
|
||||
|
||||
## Render
|
||||
|
||||
[](https://render.com/deploy?repo=https://github.com/twentyhq/twenty)
|
||||
|
||||
|
||||
## AWS Elastic Beanstalk (Coming soon)
|
||||
|
||||
A joint Docker image - containing both the frontend and server - that you can deploy using [AWS Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/) is in the works.
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
## Railway
|
||||
[](https://railway.app/template/YWGqza?referralCode=3CLObs)
|
||||
|
||||
-->
|
||||
4
packages/twenty-docs/docs/user-guide/_category_.json
Normal file
4
packages/twenty-docs/docs/user-guide/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "User Guide",
|
||||
"position": 3
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Basics",
|
||||
"position": 1
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Creating Custom Objects
|
||||
sidebar_position: 1
|
||||
sidebar_custom_props:
|
||||
icon: TbAugmentedReality
|
||||
---
|
||||
|
||||
Objects are structures that allow you to store data (records, attributes, and values) specific to an organization. Twenty provides both standard and custom objects.
|
||||
|
||||
Standard objects are in-built objects with a set of attributes available for all users. All workspaces come with three standard objects by default: People, Companies, and Opportunities. Standard objects have standard fields that are also available for all Twenty users, like Account Owner and URL.
|
||||
|
||||
Custom objects are objects that you can create to store information that is unique to your organization. They are not built-in; members of your workspace can create and customize custom objects to hold information that standard objects aren't suitable for. For example, if you're Airbnb, you may want to create a custom object for Listings or Reservations.
|
||||
|
||||
## Creating a new custom object
|
||||
|
||||
To create a new custom object:
|
||||
|
||||
1. Go to Settings in the sidebar on the left.
|
||||
2. Under Workspace, go to Data model. Here you'll be able to see an overview of all your existing Standard and Custom objects (both active and disabled).
|
||||
<br/>
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/user-guide/view-all-objects.png" alt="View all objects" />
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
3. Click on `+ New object` at the top, then choose Custom as the object type. Enter the name (both singular and plural) and description for your custom object and hit Save (at the top right). Using Listing as an example of custom object, the singular would be "listing" and the plural would be "listings" along with a description like "Listings that hosts created to showcase their property."
|
||||
|
||||
<br/>
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/user-guide/create-custom-object.gif" alt="Create custom object" />
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
4. Once you create your custom object, you'll be able to manage it. You can edit the name and description, view the different fields, and add more fields.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/user-guide/manage-custom-object.png" alt="Create custom object" />
|
||||
</div>
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Integrations",
|
||||
"position": 2
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Connect Twenty and Zapier
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbBrandZapier
|
||||
---
|
||||
|
||||
:::caution
|
||||
|
||||
Twenty integration is currently being registered in the public Zapier repository, you may not find it until the publishing process is complete.
|
||||
|
||||
:::
|
||||
|
||||
Sync Twenty with 3000+ apps using [Zapier](https://zapier.com/), and automate your work. Here's how you can connect Twenty to Zapier:
|
||||
|
||||
1. Go to Zapier and log in.
|
||||
2. Click on `+ Create Zap` in the left sidebar.
|
||||
3. Choose the application you want to set as the trigger. A trigger refers to an event that starts the automation.
|
||||
4. Select Twenty as the action. An action is the event performed whenever an application triggers an automation. [Learn more about triggers and actions in Zapier.](https://zapier.com/how-it-works)
|
||||
5. Once you choose the Twenty account that you want to use for your automation, you'll have to authorize it by adding an API key. You can learn [how to generate your API key here.](generating-api-keys.mdx)
|
||||
6. Enter your API key and click on 'Yes, Continue to Twenty.'
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/img/user-guide/connect-zapier.png" alt="Connect Twenty to Zapier" />
|
||||
</div>
|
||||
|
||||
You can now continue creating your automation!
|
||||
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Generating an API Key
|
||||
sidebar_position: 2
|
||||
sidebar_custom_props:
|
||||
icon: TbApi
|
||||
---
|
||||
|
||||
To generate an API key:
|
||||
|
||||
1. Go to Settings in the sidebar on the left.
|
||||
2. Under Workspace, go to Developers. Here, you'll see a list of active keys that you or your team have created.
|
||||
3. To generate a new key, click on `+ Create key` on the top right.
|
||||
4. Give your API key a name, an expiration date, and a logo.
|
||||
5. Hit save to see your API key.
|
||||
6. Since the key is only visible once, make sure you store it somewhere safe.
|
||||
|
||||
:::caution Note
|
||||
|
||||
Since your API key contains sensitive information, you shouldn't share it with services you don't fully trust. If leaked, someone can use it maliciously. If you think your API key is no longer secure, make sure you disable it and generate a new one.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## Regenerating API key
|
||||
|
||||
To regenerate an API key, click on the key you want to regenerate. You'll then be able to see a button to regenerate the API key.
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Others",
|
||||
"position": 3
|
||||
}
|
||||
20
packages/twenty-docs/docs/user-guide/others/glossary.mdx
Normal file
20
packages/twenty-docs/docs/user-guide/others/glossary.mdx
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Glossary
|
||||
sidebar_position: 3
|
||||
sidebar_custom_props:
|
||||
icon: TbVocabulary
|
||||
---
|
||||
|
||||
### Company & People
|
||||
The CRM has two fundamental types of records:
|
||||
- A `Company` represents a business or organization.
|
||||
- `People` represent your company's current and prospective customers or clients.
|
||||
|
||||
### Pipelines
|
||||
A `Pipeline` is a way to track a business process. Pipelines are present within a *module* and have *stages*:
|
||||
- A **module** contains the logic for a certain business process (for example: sales, recruiting).
|
||||
- **Stages** map the steps in your process (for example: new, ongoing, won, lost).
|
||||
|
||||
### Workspace
|
||||
A `Workspace` typically represents a company using Twenty.
|
||||
It has a single domain name, which is typically the domain name your company uses for employee email addresses.
|
||||
37
packages/twenty-docs/docs/user-guide/user-guide.mdx
Normal file
37
packages/twenty-docs/docs/user-guide/user-guide.mdx
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: User Guide
|
||||
displayed_sidebar: userSidebar
|
||||
sidebar_class_name: hidden
|
||||
sidebar_position: 0
|
||||
sidebar_custom_props:
|
||||
icon: TbUsers
|
||||
isSidebarRoot: true
|
||||
---
|
||||
|
||||
# Welcome to Twenty's User Guide
|
||||
|
||||
The purpose of this user guide is to help you learn how you can use Twenty to build the CRM you want.
|
||||
|
||||
This quick-start guide walks you through the basics.
|
||||
|
||||
## Quick Search
|
||||
|
||||
You'll see a search bar at the top of your sidebar. You can also bring up the command bar with the `cmd`/`ctrl` + `k` shortcut to navigate through your workspace, and find people, companies, notes, and more.
|
||||
|
||||
The command bar also supports other shortcuts for navigation.
|
||||
|
||||
## Create Pre-filtered Views
|
||||
|
||||
Twenty allows you to add filters to see data that meets certain criteria and hides the rest.
|
||||
|
||||
To create a filter in your workspace, click Filter at the top right and select the attribute you'd like to filter your records by. Create your filter and then save changes in a new view by clicking `+ Create view`.
|
||||
|
||||
The filtered view is now available to your whole team.
|
||||
|
||||
## Add Stages For Opportunities
|
||||
|
||||
You can also add more stages to the opportunities board.
|
||||
|
||||
On the Opportunities page, click on Options, Stages, then `+ Add Stage`.
|
||||
|
||||
You can also edit the stage by clicking on the name.
|
||||
124
packages/twenty-docs/docusaurus.config.js
Normal file
124
packages/twenty-docs/docusaurus.config.js
Normal file
@ -0,0 +1,124 @@
|
||||
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
import { themes } from 'prism-react-renderer';
|
||||
|
||||
const lightCodeTheme = themes.github;
|
||||
const darkCodeTheme = themes.dracula;
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: "Twenty - Documentation",
|
||||
tagline: "Twenty is cool",
|
||||
favicon: "img/logo-square-dark.ico",
|
||||
|
||||
// Prevent search engines from indexing the doc for selected environments
|
||||
noIndex: process.env.SHOULD_INDEX_DOC === "false",
|
||||
|
||||
// Set the production url of your site here
|
||||
url: "https://docs.twenty.com",
|
||||
// Set the /<baseUrl>/ pathname under which your site is served
|
||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||
baseUrl: "/",
|
||||
onBrokenLinks: "throw",
|
||||
onBrokenMarkdownLinks: "warn",
|
||||
|
||||
headTags: [],
|
||||
|
||||
// Even if you don't use internalization, you can use this field to set useful
|
||||
// metadata like html lang. For example, if your site is Chinese, you may want
|
||||
// to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en"],
|
||||
},
|
||||
presets: [
|
||||
[
|
||||
"classic",
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
breadcrumbs: false,
|
||||
sidebarPath: require.resolve("./sidebars.js"),
|
||||
sidebarCollapsible: false,
|
||||
routeBasePath: "/",
|
||||
editUrl: "https://github.com/twentyhq/twenty/edit/main/docs/",
|
||||
},
|
||||
blog: false,
|
||||
theme: {
|
||||
customCss: require.resolve("./src/css/custom.css"),
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
// Replace with your project's social card
|
||||
image: "img/social-card.png",
|
||||
colorMode: {
|
||||
defaultMode: "light",
|
||||
respectPrefersColorScheme: false,
|
||||
},
|
||||
navbar: {
|
||||
/*title: 'Twenty',*/
|
||||
logo: {
|
||||
alt: "Twenty",
|
||||
src: "img/logo-square-dark.svg",
|
||||
srcDark: "img/logo-square-light.svg",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: "search",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
to: "/user-guide",
|
||||
label: "User Guide",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
to: "https://github.com/twentyhq/twenty/releases",
|
||||
label: "Releases",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
href: "https://github.com/twentyhq/twenty",
|
||||
position: "right",
|
||||
className: "header-github-link",
|
||||
"aria-label": "GitHub repository",
|
||||
},
|
||||
],
|
||||
},
|
||||
algolia: {
|
||||
appId: "J2OX2P2QAO",
|
||||
apiKey: "e0a7a59c7862598a0cf87307c8ea97f2",
|
||||
indexName: "twenty",
|
||||
|
||||
// Optional: see doc section below
|
||||
contextualSearch: true,
|
||||
// Optional: Specify domains where the navigation should occur through window.location instead on history.push. Useful when our Algolia config crawls multiple documentation sites and we want to navigate with window.location.href to them.
|
||||
// externalUrlRegex: 'external\\.com|domain\\.com',
|
||||
// Optional: Replace parts of the item URLs from Algolia. Useful when using the same search index for multiple deployments using a different baseUrl. You can use regexp or string in the `from` param. For example: localhost:3000 vs myCompany.com/docs
|
||||
/* replaceSearchResultPathname: {
|
||||
from: '/docs/', // or as RegExp: /\/docs\//
|
||||
to: '/',
|
||||
},*/
|
||||
// Optional: Algolia search parameters
|
||||
searchParameters: {},
|
||||
// Optional: path for search page that enabled by default (`false` to disable it)
|
||||
searchPagePath: "search",
|
||||
},
|
||||
/* footer: {
|
||||
copyright: `© ${new Date().getFullYear()} Twenty. Docs generated with Docusaurus.`,
|
||||
},*/
|
||||
prism: {
|
||||
theme: lightCodeTheme,
|
||||
darkTheme: darkCodeTheme,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
56
packages/twenty-docs/package.json
Normal file
56
packages/twenty-docs/package.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "twenty-docs",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start --host 0.0.0.0 --port 5001",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codesandbox/sandpack-react": "^2.9.0",
|
||||
"@docusaurus/core": "^3.0.0",
|
||||
"@docusaurus/preset-classic": "^3.0.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
"graphiql": "^2.4.7",
|
||||
"graphql": "^16.6.0",
|
||||
"iframe-resizer-react": "^1.1.0",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.0.0",
|
||||
"@docusaurus/tsconfig": "3.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"typescript": "~5.2.2"
|
||||
},
|
||||
"overrides": {
|
||||
"trim": "^0.0.3",
|
||||
"got": "^11.8.5"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
}
|
||||
}
|
||||
45
packages/twenty-docs/sidebars.js
Normal file
45
packages/twenty-docs/sidebars.js
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
const backToHomeLink = {
|
||||
/** @type {"ref"} */
|
||||
type: "ref",
|
||||
id: "homepage",
|
||||
label: "Back to home",
|
||||
className: "menu__list-item--home",
|
||||
customProps: {
|
||||
icon: "TbArrowBackUp",
|
||||
iconSize: 20,
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
docsSidebar: [{ type: "autogenerated", dirName: "." }],
|
||||
frontendSidebar: [
|
||||
backToHomeLink,
|
||||
{ type: "autogenerated", dirName: "contributor/frontend" },
|
||||
],
|
||||
backendSidebar: [
|
||||
backToHomeLink,
|
||||
{ type: "autogenerated", dirName: "contributor/server" },
|
||||
],
|
||||
userSidebar: [
|
||||
{ type: "autogenerated", dirName: "user-guide" },
|
||||
],
|
||||
uiDocsSidebar: [
|
||||
{ type: "autogenerated", dirName: "contributor/frontend/ui-components" },
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = sidebars;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user