diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 030209c47dea..95f91b2a78be 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -1,14 +1,7 @@
module.exports = {
root: true,
extends: ['plugin:prettier/recommended'],
- plugins: [
- '@nx',
- 'prefer-arrow',
- 'import',
- 'simple-import-sort',
- 'unused-imports',
- 'unicorn',
- ],
+ plugins: ['@nx', 'prefer-arrow', 'import', 'unused-imports', 'unicorn'],
rules: {
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }],
@@ -53,26 +46,6 @@ module.exports = {
},
],
- 'simple-import-sort/imports': [
- 'error',
- {
- groups: [
- // Packages
- ['^react', '^@?\\w'],
- // Internal modules
- ['^(@|~|src|@ui)(/.*|$)'],
- // Side effect imports
- ['^\\u0000'],
- // Relative imports
- ['^\\.\\.(?!/?$)', '^\\.\\./?$'],
- ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
- // CSS imports
- ['^.+\\.?(css)$'],
- ],
- },
- ],
- 'simple-import-sort/exports': 'error',
-
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
diff --git a/.vscode/settings.json b/.vscode/settings.json
index c6ff47f129ec..d63c92973cfc 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,21 +5,24 @@
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
- "source.addMissingImports": "always"
+ "source.addMissingImports": "always",
+ "source.organizeImports": "always"
}
},
"[javascript]": {
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
- "source.addMissingImports": "always"
+ "source.addMissingImports": "always",
+ "source.organizeImports": "always"
}
},
"[typescriptreact]": {
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
- "source.addMissingImports": "always"
+ "source.addMissingImports": "always",
+ "source.organizeImports": "always"
}
},
"[json]": {
diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldEmpty.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldEmpty.ts
index 9126d761a1ba..f1e100566e72 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldEmpty.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useIsFieldEmpty.ts
@@ -9,6 +9,7 @@ import { FieldContext } from '../contexts/FieldContext';
export const useIsFieldEmpty = () => {
const { entityId, fieldDefinition, overridenIsFieldEmpty } =
useContext(FieldContext);
+
const fieldValue = useRecordFieldValue(
entityId,
fieldDefinition?.metadata?.fieldName ?? '',
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RemoveSortingModal.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRemoveSortingModal.tsx
similarity index 96%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RemoveSortingModal.tsx
rename to packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRemoveSortingModal.tsx
index 9bfb93b60430..efe7e4cb9236 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RemoveSortingModal.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexRemoveSortingModal.tsx
@@ -5,7 +5,7 @@ import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModa
import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
-export const RemoveSortingModal = ({
+export const RecordIndexRemoveSortingModal = ({
recordTableId,
}: {
recordTableId: string;
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx
index 31be052ff407..f5af2e96fa20 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainer.tsx
@@ -1,8 +1,8 @@
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext';
+import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal';
import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
-import { RemoveSortingModal } from '@/object-record/record-table/components/RemoveSortingModal';
import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu';
type RecordIndexTableContainerProps = {
@@ -39,7 +39,7 @@ export const RecordIndexTableContainer = ({
createRecord={createRecord}
/>
-
+
>
);
diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx
index 0475a925606a..2af1e91d7c21 100644
--- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellEditButton.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { IconComponent } from 'twenty-ui';
-import { AnimatedContainer } from '@/object-record/record-table/components/AnimatedContainer';
import { FloatingIconButton } from '@/ui/input/button/components/FloatingIconButton';
+import { AnimatedContainer } from '@/ui/utilities/animation/components/AnimatedContainer';
const StyledInlineCellButtonContainer = styled.div`
align-items: center;
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/GripCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/GripCell.tsx
deleted file mode 100644
index b9900026dbf6..000000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/components/GripCell.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import styled from '@emotion/styled';
-
-import { IconListViewGrip } from '@/ui/input/components/IconListViewGrip';
-
-const StyledContainer = styled.div`
- cursor: grab;
- width: 16px;
- height: 32px;
- z-index: 200;
- display: flex;
- &:hover .icon {
- opacity: 1;
- }
-`;
-
-const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
- opacity: ${({ isDragging }) => (isDragging ? 1 : 0)};
- transition: opacity 0.1s;
-`;
-
-export const GripCell = ({ isDragging }: { isDragging: boolean }) => {
- return (
-
-
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx
index 84a8e13cbdb3..5407b756dac7 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTable.tsx
@@ -1,154 +1,20 @@
-import { css } from '@emotion/react';
import styled from '@emotion/styled';
-import { useRecoilValue } from 'recoil';
-import { MOBILE_VIEWPORT, RGBA } from 'twenty-ui';
+import { isNonEmptyString } from '@sniptt/guards';
-import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
-import { RecordTableBody } from '@/object-record/record-table/components/RecordTableBody';
-import { RecordTableBodyEffect } from '@/object-record/record-table/components/RecordTableBodyEffect';
-import { RecordTableHeader } from '@/object-record/record-table/components/RecordTableHeader';
-import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
-import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
+import { RecordTableBody } from '@/object-record/record-table/record-table-body/components/RecordTableBody';
+import { RecordTableBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEffect';
+import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
+import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
-import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
-import { useCloseRecordTableCellV2 } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2';
-import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
-import {
- OpenTableCellArgs,
- useOpenRecordTableCellV2,
-} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
-import { useTriggerContextMenu } from '@/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu';
-import { useUpsertRecordV2 } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2';
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
-import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
-import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
-const StyledTable = styled.table<{
- freezeFirstColumns?: boolean;
-}>`
+const StyledTable = styled.table`
border-radius: ${({ theme }) => theme.border.radius.sm};
border-spacing: 0;
margin-right: ${({ theme }) => theme.table.horizontalCellMargin};
table-layout: fixed;
width: calc(100% - ${({ theme }) => theme.table.horizontalCellMargin} * 2);
-
- th {
- border-block: 1px solid ${({ theme }) => theme.border.color.light};
- color: ${({ theme }) => theme.font.color.tertiary};
- padding: 0;
- text-align: left;
-
- :last-child {
- border-right-color: transparent;
- }
- :first-of-type {
- border-top-color: transparent;
- border-bottom-color: transparent;
- }
- }
-
- td {
- border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
- color: ${({ theme }) => theme.font.color.primary};
- border-right: 1px solid ${({ theme }) => theme.border.color.light};
-
- padding: 0;
-
- text-align: left;
-
- :last-child {
- border-right-color: transparent;
- }
- :first-of-type {
- border-top-color: transparent;
- border-bottom-color: transparent;
- }
- }
-
- th {
- background-color: ${({ theme }) => theme.background.primary};
- border-right: 1px solid ${({ theme }) => theme.border.color.light};
- }
-
- thead th {
- position: sticky;
- top: 0;
- z-index: 9;
- }
-
- thead th:nth-of-type(1),
- thead th:nth-of-type(2),
- thead th:nth-of-type(3) {
- z-index: 12;
- background-color: ${({ theme }) => theme.background.primary};
- }
-
- thead th:nth-of-type(1) {
- width: 9px;
- left: 0;
- border-right-color: ${({ theme }) => theme.background.primary};
- }
-
- thead th:nth-of-type(2) {
- left: 9px;
- border-right-color: ${({ theme }) => theme.background.primary};
- }
-
- thead th:nth-of-type(3) {
- left: 39px;
- }
-
- tbody td:nth-of-type(1),
- tbody td:nth-of-type(2),
- tbody td:nth-of-type(3) {
- position: sticky;
- z-index: 1;
- }
-
- tbody td:nth-of-type(1) {
- left: 0;
- z-index: 7;
- }
-
- tbody td:nth-of-type(2) {
- left: 9px;
- z-index: 5;
- }
-
- tbody td:nth-of-type(3) {
- left: 39px;
- z-index: 6;
- }
-
- thead th:nth-of-type(3),
- tbody td:nth-of-type(3) {
- ${({ freezeFirstColumns }) =>
- freezeFirstColumns &&
- css`
- @media (max-width: ${MOBILE_VIEWPORT}px) {
- width: 35px;
- max-width: 35px;
- }
- `}
-
- &::after {
- content: '';
- height: calc(100% + 1px);
- position: absolute;
- width: 4px;
- right: -4px;
- top: 0;
-
- ${({ freezeFirstColumns, theme }) =>
- freezeFirstColumns &&
- css`
- box-shadow: 4px 0px 4px -4px ${theme.name === 'dark'
- ? RGBA(theme.grayScale.gray50, 0.8)
- : RGBA(theme.grayScale.gray100, 0.25)} inset;
- `}
- }
- }
`;
type RecordTableProps = {
@@ -164,97 +30,27 @@ export const RecordTable = ({
onColumnsChange,
createRecord,
}: RecordTableProps) => {
- const { scopeId, visibleTableColumnsSelector } =
- useRecordTableStates(recordTableId);
-
- const { objectMetadataItem } = useObjectMetadataItem({
- objectNameSingular,
- });
-
- const { upsertRecord } = useUpsertRecordV2({
- objectNameSingular,
- });
-
- const handleUpsertRecord = ({
- persistField,
- entityId,
- fieldName,
- }: {
- persistField: () => void;
- entityId: string;
- fieldName: string;
- }) => {
- upsertRecord(persistField, entityId, fieldName, recordTableId);
- };
-
- const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
+ const { scopeId } = useRecordTableStates(recordTableId);
- const handleOpenTableCell = (args: OpenTableCellArgs) => {
- openTableCell(args);
- };
-
- const { moveFocus } = useRecordTableMoveFocus(recordTableId);
-
- const handleMoveFocus = (direction: MoveFocusDirection) => {
- moveFocus(direction);
- };
-
- const { closeTableCell } = useCloseRecordTableCellV2(recordTableId);
-
- const handleCloseTableCell = () => {
- closeTableCell();
- };
-
- const { moveSoftFocusToCell } =
- useMoveSoftFocusToCellOnHoverV2(recordTableId);
-
- const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => {
- moveSoftFocusToCell(cellPosition);
- };
-
- const { triggerContextMenu } = useTriggerContextMenu({
- recordTableId,
- });
-
- const handleContextMenu = (event: React.MouseEvent, recordId: string) => {
- triggerContextMenu(event, recordId);
- };
-
- const { handleContainerMouseEnter } = useHandleContainerMouseEnter({
- recordTableId,
- });
-
- const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
+ if (!isNonEmptyString(objectNameSingular)) {
+ return <>>;
+ }
return (
- {!!objectNameSingular && (
-
-
-
-
-
-
-
- )}
+
+
+
+
+
+
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBody.tsx
deleted file mode 100644
index e3a83b8348a0..000000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBody.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { useRecoilValue } from 'recoil';
-
-import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/components/RecordTableBodyFetchMoreLoader';
-import { RecordTableBodyLoading } from '@/object-record/record-table/components/RecordTableBodyLoading';
-import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow';
-import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
-import { DraggableTableBody } from '@/ui/layout/draggable-list/components/DraggableTableBody';
-
-type RecordTableBodyProps = {
- objectNameSingular: string;
- recordTableId: string;
-};
-
-export const RecordTableBody = ({
- objectNameSingular,
- recordTableId,
-}: RecordTableBodyProps) => {
- const { tableRowIdsState, isRecordTableInitialLoadingState } =
- useRecordTableStates();
-
- const tableRowIds = useRecoilValue(tableRowIdsState);
-
- const isRecordTableInitialLoading = useRecoilValue(
- isRecordTableInitialLoadingState,
- );
-
- if (isRecordTableInitialLoading && tableRowIds.length === 0) {
- return ;
- }
-
- return (
- <>
-
- {tableRowIds.map((recordId, rowIndex) => {
- return (
-
- );
- })}
- >
- }
- />
-
- >
- );
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffect.tsx
deleted file mode 100644
index 508c2c8c8ffd..000000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyEffect.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { useEffect } from 'react';
-import { useRecoilValue } from 'recoil';
-import { useDebouncedCallback } from 'use-debounce';
-
-import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
-import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
-import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
-import { useScrollRestoration } from '~/hooks/useScrollRestoration';
-
-type RecordTableBodyEffectProps = {
- objectNameSingular: string;
-};
-
-export const RecordTableBodyEffect = ({
- objectNameSingular,
-}: RecordTableBodyEffectProps) => {
- const {
- fetchMoreRecords: fetchMoreObjects,
- records,
- totalCount,
- setRecordTableData,
- loading,
- queryStateIdentifier,
- } = useLoadRecordIndexTable(objectNameSingular);
-
- const isFetchingMoreObjects = useRecoilValue(
- isFetchingMoreRecordsFamilyState(queryStateIdentifier),
- );
-
- const { tableLastRowVisibleState } = useRecordTableStates();
-
- const tableLastRowVisible = useRecoilValue(tableLastRowVisibleState);
-
- const rowHeight = 32;
- const viewportHeight = records.length * rowHeight;
-
- useScrollRestoration(viewportHeight);
-
- useEffect(() => {
- if (!loading) {
- setRecordTableData(records, totalCount);
- }
- }, [records, totalCount, setRecordTableData, loading]);
-
- const fetchMoreDebouncedIfRequested = useDebouncedCallback(async () => {
- // We are debouncing here to give the user some room to scroll if they want to within this throttle window
- await fetchMoreObjects();
- }, 100);
-
- useEffect(() => {
- if (!isFetchingMoreObjects && tableLastRowVisible) {
- fetchMoreDebouncedIfRequested();
- }
- }, [
- fetchMoreDebouncedIfRequested,
- isFetchingMoreObjects,
- tableLastRowVisible,
- ]);
-
- return <>>;
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx
new file mode 100644
index 000000000000..7925fb8fbf16
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx
@@ -0,0 +1,109 @@
+import { ReactNode } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
+import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
+import { useCloseRecordTableCellV2 } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2';
+import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
+import {
+ OpenTableCellArgs,
+ useOpenRecordTableCellV2,
+} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
+import { useTriggerContextMenu } from '@/object-record/record-table/record-table-cell/hooks/useTriggerContextMenu';
+import { useUpsertRecordV2 } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecordV2';
+import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
+import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
+
+export const RecordTableContextProvider = ({
+ recordTableId,
+ objectNameSingular,
+ children,
+}: {
+ recordTableId: string;
+ objectNameSingular: string;
+ children: ReactNode;
+}) => {
+ const { visibleTableColumnsSelector } = useRecordTableStates(recordTableId);
+
+ const { objectMetadataItem } = useObjectMetadataItem({
+ objectNameSingular,
+ });
+
+ const { upsertRecord } = useUpsertRecordV2({
+ objectNameSingular,
+ });
+
+ const handleUpsertRecord = ({
+ persistField,
+ entityId,
+ fieldName,
+ }: {
+ persistField: () => void;
+ entityId: string;
+ fieldName: string;
+ }) => {
+ upsertRecord(persistField, entityId, fieldName, recordTableId);
+ };
+
+ const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
+
+ const handleOpenTableCell = (args: OpenTableCellArgs) => {
+ openTableCell(args);
+ };
+
+ const { moveFocus } = useRecordTableMoveFocus(recordTableId);
+
+ const handleMoveFocus = (direction: MoveFocusDirection) => {
+ moveFocus(direction);
+ };
+
+ const { closeTableCell } = useCloseRecordTableCellV2(recordTableId);
+
+ const handleCloseTableCell = () => {
+ closeTableCell();
+ };
+
+ const { moveSoftFocusToCell } =
+ useMoveSoftFocusToCellOnHoverV2(recordTableId);
+
+ const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => {
+ moveSoftFocusToCell(cellPosition);
+ };
+
+ const { triggerContextMenu } = useTriggerContextMenu({
+ recordTableId,
+ });
+
+ const handleContextMenu = (event: React.MouseEvent, recordId: string) => {
+ triggerContextMenu(event, recordId);
+ };
+
+ const { handleContainerMouseEnter } = useHandleContainerMouseEnter({
+ recordTableId,
+ });
+
+ const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeader.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeader.tsx
deleted file mode 100644
index 5310ae1f11de..000000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeader.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import { useTheme } from '@emotion/react';
-import styled from '@emotion/styled';
-import { useRecoilValue } from 'recoil';
-import { IconPlus } from 'twenty-ui';
-
-import { RecordTableHeaderCell } from '@/object-record/record-table/components/RecordTableHeaderCell';
-import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
-import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
-import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
-
-import { RecordTableHeaderPlusButtonContent } from './RecordTableHeaderPlusButtonContent';
-import { SelectAllCheckbox } from './SelectAllCheckbox';
-
-const StyledTableHead = styled.thead`
- cursor: pointer;
-`;
-
-const StyledPlusIconHeaderCell = styled.th<{ isTableWiderThanScreen: boolean }>`
- ${({ theme }) => {
- return `
- &:hover {
- background: ${theme.background.transparent.light};
- };
- padding-left: ${theme.spacing(3)};
- `;
- }};
- border-left: none !important;
- min-width: 32px;
- ${({ isTableWiderThanScreen, theme }) =>
- isTableWiderThanScreen &&
- `
- width: 32px;
- border-right: none !important;
- background-color: ${theme.background.primary};
- `};
- z-index: 1;
-`;
-
-const StyledPlusIconContainer = styled.div`
- align-items: center;
- display: flex;
- height: 32px;
- justify-content: center;
- width: 32px;
-`;
-
-export const HIDDEN_TABLE_COLUMN_DROPDOWN_ID =
- 'hidden-table-columns-dropdown-scope-id';
-
-const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
- 'hidden-table-columns-dropdown-hotkey-scope-id';
-
-export const RecordTableHeader = ({
- createRecord,
-}: {
- createRecord: () => void;
-}) => {
- const { visibleTableColumnsSelector, hiddenTableColumnsSelector } =
- useRecordTableStates();
-
- const scrollWrapper = useScrollWrapperScopedRef();
- const isTableWiderThanScreen =
- (scrollWrapper.current?.clientWidth ?? 0) <
- (scrollWrapper.current?.scrollWidth ?? 0);
-
- const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
- const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector());
-
- const theme = useTheme();
-
- return (
-
-
- |
-
-
- |
- {visibleTableColumns.map((column) => (
-
- ))}
-
- {hiddenTableColumns.length > 0 && (
-
-
-
- }
- dropdownComponents={}
- dropdownPlacement="bottom-start"
- dropdownHotkeyScope={{
- scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
- }}
- />
- )}
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRow.tsx
deleted file mode 100644
index 2f7112a25538..000000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRow.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import { useContext } from 'react';
-import { useInView } from 'react-intersection-observer';
-import { useTheme } from '@emotion/react';
-import styled from '@emotion/styled';
-import { Draggable } from '@hello-pangea/dnd';
-import { useRecoilValue } from 'recoil';
-
-import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
-import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
-import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/components/RecordTableCellFieldContextWrapper';
-import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
-import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
-import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
-import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
-import { ScrollWrapperContext } from '@/ui/utilities/scroll/components/ScrollWrapper';
-
-import { CheckboxCell } from './CheckboxCell';
-import { GripCell } from './GripCell';
-
-type RecordTableRowProps = {
- recordId: string;
- rowIndex: number;
- isPendingRow?: boolean;
-};
-
-export const StyledTd = styled.td<{ isSelected?: boolean }>`
- background: ${({ theme }) => theme.background.primary};
- position: relative;
- user-select: none;
-
- ${({ isSelected, theme }) =>
- isSelected &&
- `
- background: ${theme.accent.quaternary};
-
- `}
-`;
-
-export const StyledTr = styled.tr<{ isDragging: boolean }>`
- border: 1px solid transparent;
- transition: border-left-color 0.2s ease-in-out;
-
- td:nth-of-type(-n + 2) {
- border-right-color: ${({ theme }) => theme.background.primary};
- }
-
- ${({ isDragging }) =>
- isDragging &&
- `
- td:nth-of-type(1) {
- background-color: transparent;
- border-color: transparent;
- }
-
- td:nth-of-type(2) {
- background-color: transparent;
- border-color: transparent;
- }
-
- td:nth-of-type(3) {
- background-color: transparent;
- border-color: transparent;
- }
-
- `}
-`;
-
-const SelectableStyledTd = ({
- isSelected,
- children,
- style,
-}: {
- isSelected: boolean;
- children?: React.ReactNode;
- style?: React.CSSProperties;
-}) => (
-
- {children}
-
-);
-
-export const RecordTableRow = ({
- recordId,
- rowIndex,
- isPendingRow,
-}: RecordTableRowProps) => {
- const { visibleTableColumnsSelector, isRowSelectedFamilyState } =
- useRecordTableStates();
- const currentRowSelected = useRecoilValue(isRowSelectedFamilyState(recordId));
- const { objectMetadataItem } = useContext(RecordTableContext);
-
- const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
-
- const scrollWrapperRef = useContext(ScrollWrapperContext);
-
- const { ref: elementRef, inView } = useInView({
- root: scrollWrapperRef.current?.querySelector(
- '[data-overlayscrollbars-viewport="scrollbarHidden"]',
- ),
- rootMargin: '1000px',
- });
-
- const theme = useTheme();
-
- return (
-
-
-
-
- {(draggableProvided, draggableSnapshot) => (
- {
- elementRef(node);
- draggableProvided.innerRef(node);
- }}
- // eslint-disable-next-line react/jsx-props-no-spreading
- {...draggableProvided.draggableProps}
- style={{
- ...draggableProvided.draggableProps.style,
- background: draggableSnapshot.isDragging
- ? theme.background.transparent.light
- : 'none',
- borderColor: draggableSnapshot.isDragging
- ? `${theme.border.color.medium}`
- : 'transparent',
- }}
- isDragging={draggableSnapshot.isDragging}
- data-testid={`row-id-${recordId}`}
- data-selectable-id={recordId}
- >
-
-
-
-
- {!draggableSnapshot.isDragging && }
-
- {inView || draggableSnapshot.isDragging
- ? visibleTableColumns.map((column, columnIndex) => (
-
- {draggableSnapshot.isDragging && columnIndex > 0 ? null : (
-
- )}
-
- ))
- : visibleTableColumns.map((column) => (
-
- ))}
-
-
- )}
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRows.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRows.tsx
new file mode 100644
index 000000000000..40db65bbb80c
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableRows.tsx
@@ -0,0 +1,16 @@
+import { useRecoilValue } from 'recoil';
+
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
+
+export const RecordTableRows = () => {
+ const { tableRowIdsState } = useRecordTableStates();
+
+ const tableRowIds = useRecoilValue(tableRowIdsState);
+
+ return tableRowIds.map((recordId, rowIndex) => {
+ return (
+
+ );
+ });
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx
index 63cced18d373..2161a2fe0b13 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx
@@ -75,6 +75,28 @@ export const RecordTableWithWrappers = ({
const isRemote = foundObjectMetadataItem?.isRemote ?? false;
+ const handleColumnsChange = useRecoilCallback(
+ () => (columns) => {
+ saveViewFields(
+ mapColumnDefinitionsToViewFields(
+ columns as ColumnDefinition[],
+ ),
+ );
+ },
+ [saveViewFields],
+ );
+
+ if (!isRecordTableInitialLoading && tableRowIds.length === 0) {
+ return (
+
+ );
+ }
+
return (
@@ -85,16 +107,7 @@ export const RecordTableWithWrappers = ({
(columns) => {
- saveViewFields(
- mapColumnDefinitionsToViewFields(
- columns as ColumnDefinition[],
- ),
- );
- },
- [saveViewFields],
- )}
+ onColumnsChange={handleColumnsChange}
createRecord={createRecord}
/>
- {!isRecordTableInitialLoading &&
- // we cannot rely on count states because this is not available for remote objects
- tableRowIds.length === 0 && (
-
- )}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx
index 487465b609a2..5720a2292cdb 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx
@@ -1,5 +1,5 @@
-import { useEffect } from 'react';
import { Meta, StoryObj } from '@storybook/react';
+import { useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { ComponentDecorator } from 'twenty-ui';
@@ -12,7 +12,6 @@ import {
useSetRecordValue,
} from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
-import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/components/RecordTableCellFieldContextWrapper';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
@@ -21,6 +20,7 @@ import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDeco
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
+import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
import { mockPerformance } from './mock';
const objectMetadataItems = getObjectMetadataItemsMock();
@@ -73,6 +73,9 @@ const meta: Meta = {
onContextMenu: () => {},
onCellMouseEnter: () => {},
visibleTableColumns: mockPerformance.visibleTableColumns as any,
+ objectNameSingular:
+ mockPerformance.objectMetadataItem.nameSingular,
+ recordTableId: 'recordTableId',
}}
>
;
columnIndex: number;
+ isInEditMode: boolean;
+ hasSoftFocus: boolean;
+ cellPosition: TableCellPosition;
};
-export const RecordTableCellContext = createContext(
- {} as RecordTableRowContextProps,
-);
+export const RecordTableCellContext =
+ createContext({} as RecordTableCellContextProps);
diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts
index 4d25606854c0..9bab734d9037 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts
@@ -26,6 +26,8 @@ export type RecordTableContextProps = {
onContextMenu: (event: React.MouseEvent, recordId: string) => void;
onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void;
visibleTableColumns: ColumnDefinition[];
+ recordTableId: string;
+ objectNameSingular: string;
};
export const RecordTableContext = createContext(
diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableRowContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableRowContext.ts
index f04afe492820..d0ed1aea296b 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableRowContext.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableRowContext.ts
@@ -1,4 +1,5 @@
import { createContext } from 'react';
+import { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
export type RecordTableRowContextProps = {
pathToShowPage: string;
@@ -8,6 +9,9 @@ export type RecordTableRowContextProps = {
isSelected: boolean;
isReadOnly: boolean;
isPendingRow?: boolean;
+ isDragging: boolean;
+ dragHandleProps: DraggableProvidedDragHandleProps | null;
+ inView?: boolean;
};
export const RecordTableRowContext = createContext(
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx
new file mode 100644
index 000000000000..2a6e46c21987
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBody.tsx
@@ -0,0 +1,38 @@
+import { useRecoilValue } from 'recoil';
+
+import { RecordTableRows } from '@/object-record/record-table/components/RecordTableRows';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableBodyDragDropContext } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext';
+import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
+import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFetchMoreLoader';
+import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
+import { RecordTablePendingRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRow';
+import { useContext } from 'react';
+
+export const RecordTableBody = () => {
+ const { tableRowIdsState, isRecordTableInitialLoadingState } =
+ useRecordTableStates();
+
+ const { objectNameSingular } = useContext(RecordTableContext);
+
+ const tableRowIds = useRecoilValue(tableRowIdsState);
+
+ const isRecordTableInitialLoading = useRecoilValue(
+ isRecordTableInitialLoadingState,
+ );
+
+ if (isRecordTableInitialLoading && tableRowIds.length === 0) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/ui/layout/draggable-list/components/DraggableTableBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext.tsx
similarity index 58%
rename from packages/twenty-front/src/modules/ui/layout/draggable-list/components/DraggableTableBody.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext.tsx
index c1513d828b74..ae352714590e 100644
--- a/packages/twenty-front/src/modules/ui/layout/draggable-list/components/DraggableTableBody.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContext.tsx
@@ -1,42 +1,30 @@
-import { useState } from 'react';
-import styled from '@emotion/styled';
-import { DragDropContext, Droppable, DropResult } from '@hello-pangea/dnd';
+import { ReactNode, useContext } from 'react';
+import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { useRecoilValue, useSetRecoilState } from 'recoil';
-import { v4 } from 'uuid';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
-import { RecordTablePendingRow } from '@/object-record/record-table/components/RecordTablePendingRow';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useComputeNewRowPosition } from '@/object-record/record-table/hooks/useComputeNewRowPosition';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { isDefined } from '~/utils/isDefined';
-type DraggableTableBodyProps = {
- draggableItems: React.ReactNode;
- objectNameSingular: string;
- recordTableId: string;
-};
-
-const StyledTbody = styled.tbody`
- overflow: hidden;
-`;
+export const RecordTableBodyDragDropContext = ({
+ children,
+}: {
+ children: ReactNode;
+}) => {
+ const { objectNameSingular, recordTableId } = useContext(RecordTableContext);
-export const DraggableTableBody = ({
- objectNameSingular,
- draggableItems,
- recordTableId,
-}: DraggableTableBodyProps) => {
- const [v4Persistable] = useState(v4());
+ const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
+ objectNameSingular,
+ });
const { tableRowIdsState } = useRecordTableStates();
const tableRowIds = useRecoilValue(tableRowIdsState);
- const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
- objectNameSingular,
- });
-
const { currentViewWithCombinedFiltersAndSorts } =
useGetCurrentView(recordTableId);
@@ -45,6 +33,7 @@ export const DraggableTableBody = ({
const setIsRemoveSortingModalOpenState = useSetRecoilState(
isRemoveSortingModalOpenState,
);
+
const computeNewRowPosition = useComputeNewRowPosition();
const handleDragEnd = (result: DropResult) => {
@@ -68,20 +57,6 @@ export const DraggableTableBody = ({
};
return (
-
-
- {(provided) => (
-
-
- {draggableItems}
- {provided.placeholder}
-
- )}
-
-
+ {children}
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDroppable.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDroppable.tsx
new file mode 100644
index 000000000000..34aba9037061
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyDroppable.tsx
@@ -0,0 +1,57 @@
+import { Theme } from '@emotion/react';
+import { Droppable } from '@hello-pangea/dnd';
+import { styled } from '@linaria/react';
+import { ReactNode, useContext, useState } from 'react';
+import { ThemeContext } from 'twenty-ui';
+import { v4 } from 'uuid';
+
+const StyledTbody = styled.tbody<{
+ theme: Theme;
+}>`
+ overflow: hidden;
+
+ &.first-columns-sticky {
+ td:nth-child(1) {
+ position: sticky;
+ left: 0;
+ z-index: 5;
+ }
+ td:nth-child(2) {
+ position: sticky;
+ left: 9px;
+ z-index: 5;
+ }
+ td:nth-child(3) {
+ position: sticky;
+ left: 39px;
+ z-index: 5;
+ }
+ }
+`;
+
+export const RecordTableBodyDroppable = ({
+ children,
+}: {
+ children: ReactNode;
+}) => {
+ const [v4Persistable] = useState(v4());
+
+ const { theme } = useContext(ThemeContext);
+
+ return (
+
+ {(provided) => (
+
+ {children}
+ {provided.placeholder}
+
+ )}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEffect.tsx
new file mode 100644
index 000000000000..2ba836121002
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEffect.tsx
@@ -0,0 +1,106 @@
+import { useContext, useEffect } from 'react';
+import { useRecoilValue } from 'recoil';
+import { useDebouncedCallback } from 'use-debounce';
+
+import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
+import { isRecordTableScrolledTopComponentState } from '@/object-record/record-table/states/isRecordTableScrolledTopComponentState';
+import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
+import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
+import { scrollTopState } from '@/ui/utilities/scroll/states/scrollTopState';
+import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
+import { useScrollRestoration } from '~/hooks/useScrollRestoration';
+
+export const RecordTableBodyEffect = () => {
+ const { objectNameSingular } = useContext(RecordTableContext);
+
+ const {
+ fetchMoreRecords: fetchMoreObjects,
+ records,
+ totalCount,
+ setRecordTableData,
+ loading,
+ queryStateIdentifier,
+ } = useLoadRecordIndexTable(objectNameSingular);
+
+ const isFetchingMoreObjects = useRecoilValue(
+ isFetchingMoreRecordsFamilyState(queryStateIdentifier),
+ );
+
+ const { tableLastRowVisibleState } = useRecordTableStates();
+
+ const tableLastRowVisible = useRecoilValue(tableLastRowVisibleState);
+
+ const scrollTop = useRecoilValue(scrollTopState);
+ const setIsRecordTableScrolledTop = useSetRecoilComponentState(
+ isRecordTableScrolledTopComponentState,
+ );
+
+ useEffect(() => {
+ setIsRecordTableScrolledTop(scrollTop === 0);
+ if (scrollTop > 0) {
+ document
+ .getElementById('record-table-header')
+ ?.classList.add('header-sticky');
+ } else {
+ document
+ .getElementById('record-table-header')
+ ?.classList.remove('header-sticky');
+ }
+ }, [scrollTop, setIsRecordTableScrolledTop]);
+
+ const scrollLeft = useRecoilValue(scrollLeftState);
+
+ const setIsRecordTableScrolledLeft = useSetRecoilComponentState(
+ isRecordTableScrolledLeftComponentState,
+ );
+
+ useEffect(() => {
+ setIsRecordTableScrolledLeft(scrollLeft === 0);
+ if (scrollLeft > 0) {
+ document
+ .getElementById('record-table-body')
+ ?.classList.add('first-columns-sticky');
+ document
+ .getElementById('record-table-header')
+ ?.classList.add('first-columns-sticky');
+ } else {
+ document
+ .getElementById('record-table-body')
+ ?.classList.remove('first-columns-sticky');
+ document
+ .getElementById('record-table-header')
+ ?.classList.remove('first-columns-sticky');
+ }
+ }, [scrollLeft, setIsRecordTableScrolledLeft]);
+
+ const rowHeight = 32;
+ const viewportHeight = records.length * rowHeight;
+
+ useScrollRestoration(viewportHeight);
+
+ useEffect(() => {
+ if (!loading) {
+ setRecordTableData(records, totalCount);
+ }
+ }, [records, totalCount, setRecordTableData, loading]);
+
+ const fetchMoreDebouncedIfRequested = useDebouncedCallback(async () => {
+ // We are debouncing here to give the user some room to scroll if they want to within this throttle window
+ await fetchMoreObjects();
+ }, 100);
+
+ useEffect(() => {
+ if (!isFetchingMoreObjects && tableLastRowVisible) {
+ fetchMoreDebouncedIfRequested();
+ }
+ }, [
+ fetchMoreDebouncedIfRequested,
+ isFetchingMoreObjects,
+ tableLastRowVisible,
+ ]);
+
+ return <>>;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyFetchMoreLoader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFetchMoreLoader.tsx
similarity index 100%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyFetchMoreLoader.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFetchMoreLoader.tsx
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyLoading.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx
similarity index 64%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyLoading.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx
index 0f89384ec0e5..8a80403ded4f 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableBodyLoading.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx
@@ -1,13 +1,10 @@
import { useRecoilValue } from 'recoil';
-import { CheckboxCell } from '@/object-record/record-table/components/CheckboxCell';
-import { GripCell } from '@/object-record/record-table/components/GripCell';
-import {
- StyledTd,
- StyledTr,
-} from '@/object-record/record-table/components/RecordTableRow';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
+import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
import { RecordTableCellLoading } from '@/object-record/record-table/record-table-cell/components/RecordTableCellLoading';
+import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
export const RecordTableBodyLoading = () => {
const { visibleTableColumnsSelector } = useRecordTableStates();
@@ -16,22 +13,18 @@ export const RecordTableBodyLoading = () => {
return (
{Array.from({ length: 8 }).map((_, rowIndex) => (
-
-
-
-
-
-
-
+
+
{visibleTableColumns.map((column) => (
))}
-
+
))}
);
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx
index a38d61a39dc5..4d6fcdd74fab 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCell.tsx
@@ -1,109 +1,13 @@
-import { useContext } from 'react';
-
import { FieldDisplay } from '@/object-record/record-field/components/FieldDisplay';
-import { FieldInput } from '@/object-record/record-field/components/FieldInput';
-import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
-import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
-import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
-import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer';
-import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
-
-export const RecordTableCell = ({
- customHotkeyScope,
-}: {
- customHotkeyScope: HotkeyScope;
-}) => {
- const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
- useContext(RecordTableContext);
- const { entityId, fieldDefinition } = useContext(FieldContext);
- const { isReadOnly } = useContext(RecordTableRowContext);
-
- const handleEnter: FieldInputEvent = (persistField) => {
- onUpsertRecord({
- persistField,
- entityId,
- fieldName: fieldDefinition.metadata.fieldName,
- });
-
- onCloseTableCell();
- onMoveFocus('down');
- };
-
- const handleSubmit: FieldInputEvent = (persistField) => {
- onUpsertRecord({
- persistField,
- entityId,
- fieldName: fieldDefinition.metadata.fieldName,
- });
-
- onCloseTableCell();
- };
-
- const handleCancel = () => {
- onCloseTableCell();
- };
-
- const handleClickOutside: FieldInputEvent = (persistField) => {
- onUpsertRecord({
- persistField,
- entityId,
- fieldName: fieldDefinition.metadata.fieldName,
- });
-
- onCloseTableCell();
- };
-
- const handleEscape: FieldInputEvent = (persistField) => {
- onUpsertRecord({
- persistField,
- entityId,
- fieldName: fieldDefinition.metadata.fieldName,
- });
-
- onCloseTableCell();
- };
-
- const handleTab: FieldInputEvent = (persistField) => {
- onUpsertRecord({
- persistField,
- entityId,
- fieldName: fieldDefinition.metadata.fieldName,
- });
-
- onCloseTableCell();
- onMoveFocus('right');
- };
-
- const handleShiftTab: FieldInputEvent = (persistField) => {
- onUpsertRecord({
- persistField,
- entityId,
- fieldName: fieldDefinition.metadata.fieldName,
- });
-
- onCloseTableCell();
- onMoveFocus('left');
- };
+import { RecordTableCellFieldInput } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput';
+export const RecordTableCell = () => {
return (
- }
+ editModeContent={}
nonEditModeContent={}
/>
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx
new file mode 100644
index 000000000000..3a2a8c5c9bf8
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer.tsx
@@ -0,0 +1,101 @@
+import { ReactNode, useContext } from 'react';
+import { styled } from '@linaria/react';
+import { BORDER_COMMON, ThemeContext } from 'twenty-ui';
+
+import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
+import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
+import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
+import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import {
+ DEFAULT_CELL_SCOPE,
+ useOpenRecordTableCellFromCell,
+} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
+
+const StyledBaseContainer = styled.div<{
+ hasSoftFocus: boolean;
+ fontColorExtraLight: string;
+ backgroundColorTransparentSecondary: string;
+}>`
+ align-items: center;
+ box-sizing: border-box;
+ cursor: pointer;
+ display: flex;
+ height: 32px;
+ position: relative;
+ user-select: none;
+
+ background: ${({ hasSoftFocus, backgroundColorTransparentSecondary }) =>
+ hasSoftFocus ? backgroundColorTransparentSecondary : 'none'};
+
+ border-radius: ${({ hasSoftFocus }) =>
+ hasSoftFocus ? BORDER_COMMON.radius.sm : 'none'};
+
+ outline: ${({ hasSoftFocus, fontColorExtraLight }) =>
+ hasSoftFocus ? `1px solid ${fontColorExtraLight}` : 'none'};
+`;
+
+export const RecordTableCellBaseContainer = ({
+ children,
+}: {
+ children: ReactNode;
+}) => {
+ const { setIsFocused } = useFieldFocus();
+ const { openTableCell } = useOpenRecordTableCellFromCell();
+ const { theme } = useContext(ThemeContext);
+ const { recordId } = useContext(RecordTableRowContext);
+
+ const { hasSoftFocus, cellPosition } = useContext(RecordTableCellContext);
+
+ const { onMoveSoftFocusToCell, onCellMouseEnter } =
+ useContext(RecordTableContext);
+
+ const handleContainerMouseMove = () => {
+ setIsFocused(true);
+ if (!hasSoftFocus) {
+ onCellMouseEnter({
+ cellPosition,
+ });
+ }
+ };
+
+ const handleContainerMouseLeave = () => {
+ setIsFocused(false);
+ };
+
+ const handleContainerClick = () => {
+ if (!hasSoftFocus) {
+ onMoveSoftFocusToCell(cellPosition);
+ openTableCell();
+ }
+ };
+
+ const { onContextMenu } = useContext(RecordTableContext);
+
+ const handleContextMenu = (event: React.MouseEvent) => {
+ onContextMenu(event, recordId);
+ };
+
+ const { hotkeyScope } = useContext(FieldContext);
+
+ const editHotkeyScope = { scope: hotkeyScope } ?? DEFAULT_CELL_SCOPE;
+
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellButton.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellButton.tsx
index 287df8331f07..25b0e10f6379 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellButton.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellButton.tsx
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { IconComponent } from 'twenty-ui';
-import { AnimatedContainer } from '@/object-record/record-table/components/AnimatedContainer';
import { FloatingIconButton } from '@/ui/input/button/components/FloatingIconButton';
+import { AnimatedContainer } from '@/ui/utilities/animation/components/AnimatedContainer';
const StyledButtonContainer = styled.div`
margin: ${({ theme }) => theme.spacing(1)};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/CheckboxCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx
similarity index 75%
rename from packages/twenty-front/src/modules/object-record/record-table/components/CheckboxCell.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx
index 74af8f6ab5a9..a261fa2ae375 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/CheckboxCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox.tsx
@@ -1,9 +1,10 @@
-import { useCallback, useContext } from 'react';
import styled from '@emotion/styled';
+import { useCallback, useContext } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
import { Checkbox } from '@/ui/input/components/Checkbox';
import { actionBarOpenState } from '@/ui/navigation/action-bar/states/actionBarIsOpenState';
@@ -18,7 +19,9 @@ const StyledContainer = styled.div`
justify-content: center;
`;
-export const CheckboxCell = () => {
+export const RecordTableCellCheckbox = () => {
+ const { isSelected } = useContext(RecordTableRowContext);
+
const { recordId } = useContext(RecordTableRowContext);
const { isRowSelectedFamilyState } = useRecordTableStates();
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
@@ -31,8 +34,10 @@ export const CheckboxCell = () => {
}, [currentRowSelected, setActionBarOpenState, setCurrentRowSelected]);
return (
-
-
-
+
+
+
+
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx
index a120b9ebba44..0197e7e02697 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellContainer.tsx
@@ -1,176 +1,41 @@
-import React, { ReactElement, useContext } from 'react';
-import { styled } from '@linaria/react';
-import { useRecoilValue } from 'recoil';
-import { BORDER_COMMON, ThemeContext } from 'twenty-ui';
+import { ReactElement, useContext } from 'react';
-import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
-import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
-import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
+import { RecordTableCellBaseContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellBaseContainer';
import { RecordTableCellSoftFocusMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellSoftFocusMode';
-import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition';
-import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
-import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
-import { isSoftFocusOnTableCellComponentFamilyState } from '@/object-record/record-table/states/isSoftFocusOnTableCellComponentFamilyState';
-import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
-import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
-import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
-import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
-import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState';
-
-import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
-import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode';
import { RecordTableCellEditMode } from './RecordTableCellEditMode';
-const StyledTd = styled.td<{
- isInEditMode: boolean;
- backgroundColor: string;
-}>`
- background: ${({ backgroundColor }) => backgroundColor};
- z-index: ${({ isInEditMode }) => (isInEditMode ? '4 !important' : 3)};
-`;
-
-const borderRadiusSm = BORDER_COMMON.radius.sm;
-
-const StyledBaseContainer = styled.div<{
- hasSoftFocus: boolean;
- fontColorExtraLight: string;
- backgroundColorTransparentSecondary: string;
-}>`
- align-items: center;
- box-sizing: border-box;
- cursor: pointer;
- display: flex;
- height: 32px;
- position: relative;
- user-select: none;
-
- background: ${({ hasSoftFocus, backgroundColorTransparentSecondary }) =>
- hasSoftFocus ? backgroundColorTransparentSecondary : 'none'};
-
- border-radius: ${({ hasSoftFocus }) =>
- hasSoftFocus ? borderRadiusSm : 'none'};
-
- border: ${({ hasSoftFocus, fontColorExtraLight }) =>
- hasSoftFocus ? `1px solid ${fontColorExtraLight}` : 'none'};
-`;
-
export type RecordTableCellContainerProps = {
editModeContent: ReactElement;
nonEditModeContent: ReactElement;
- editHotkeyScope?: HotkeyScope;
transparent?: boolean;
maxContentWidth?: number;
onSubmit?: () => void;
onCancel?: () => void;
};
-const DEFAULT_CELL_SCOPE: HotkeyScope = {
- scope: TableHotkeyScope.CellEditMode,
-};
-
export const RecordTableCellContainer = ({
editModeContent,
nonEditModeContent,
- editHotkeyScope,
}: RecordTableCellContainerProps) => {
- const { theme } = useContext(ThemeContext);
-
- const { setIsFocused } = useFieldFocus();
- const { openTableCell } = useOpenRecordTableCellFromCell();
-
- const { isSelected, recordId } = useContext(RecordTableRowContext);
-
- const { onMoveSoftFocusToCell, onContextMenu, onCellMouseEnter } =
- useContext(RecordTableContext);
-
- const tableScopeId = useAvailableScopeIdOrThrow(
- RecordTableScopeInternalContext,
- getScopeIdOrUndefinedFromComponentId(),
- );
-
- const isTableCellInEditModeFamilyState = extractComponentFamilyState(
- isTableCellInEditModeComponentFamilyState,
- tableScopeId,
- );
-
- const isSoftFocusOnTableCellFamilyState = extractComponentFamilyState(
- isSoftFocusOnTableCellComponentFamilyState,
- tableScopeId,
- );
-
- const cellPosition = useCurrentTableCellPosition();
-
- const isInEditMode = useRecoilValue(
- isTableCellInEditModeFamilyState(cellPosition),
- );
-
- const hasSoftFocus = useRecoilValue(
- isSoftFocusOnTableCellFamilyState(cellPosition),
- );
-
- const handleContextMenu = (event: React.MouseEvent) => {
- onContextMenu(event, recordId);
- };
-
- const handleContainerMouseMove = () => {
- setIsFocused(true);
- if (!hasSoftFocus) {
- onCellMouseEnter({
- cellPosition,
- });
- }
- };
-
- const handleContainerMouseLeave = () => {
- setIsFocused(false);
- };
-
- const handleContainerClick = () => {
- if (!hasSoftFocus) {
- onMoveSoftFocusToCell(cellPosition);
- openTableCell();
- }
- };
-
- const tdBackgroundColor = isSelected
- ? theme.accent.quaternary
- : theme.background.primary;
+ const { hasSoftFocus, isInEditMode } = useContext(RecordTableCellContext);
return (
-
-
-
- {isInEditMode ? (
- {editModeContent}
- ) : hasSoftFocus ? (
-
- ) : (
-
- {nonEditModeContent}
-
- )}
-
-
-
+
+ {isInEditMode ? (
+ {editModeContent}
+ ) : hasSoftFocus ? (
+
+ ) : (
+
+ {nonEditModeContent}
+
+ )}
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayContainer.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayContainer.tsx
index 695689ec6682..20941b7c63a6 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayContainer.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellDisplayContainer.tsx
@@ -10,8 +10,6 @@ const StyledOuterContainer = styled.div<{
overflow: hidden;
padding-left: 6px;
width: 100%;
-
- margin: ${({ hasSoftFocus }) => (hasSoftFocus === true ? '-1px' : 'none')};
`;
const StyledInnerContainer = styled.div`
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellFieldContextWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper.tsx
similarity index 89%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellFieldContextWrapper.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper.tsx
index ec30ebe03eba..16a571fcd5cc 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableCellFieldContextWrapper.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper.tsx
@@ -1,4 +1,4 @@
-import { useContext } from 'react';
+import { ReactNode, useContext } from 'react';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
@@ -8,13 +8,16 @@ import { RecordUpdateContext } from '@/object-record/record-table/contexts/Entit
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
-import { RecordTableCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCell';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
-export const RecordTableCellFieldContextWrapper = () => {
+export const RecordTableCellFieldContextWrapper = ({
+ children,
+}: {
+ children: ReactNode;
+}) => {
const { objectMetadataItem } = useContext(RecordTableContext);
const { columnDefinition } = useContext(RecordTableCellContext);
const { recordId, pathToShowPage } = useContext(RecordTableRowContext);
@@ -49,7 +52,7 @@ export const RecordTableCellFieldContextWrapper = () => {
}),
}}
>
-
+ {children}
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput.tsx
new file mode 100644
index 000000000000..8b56f8d24b39
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput.tsx
@@ -0,0 +1,95 @@
+import { useContext } from 'react';
+
+import { FieldInput } from '@/object-record/record-field/components/FieldInput';
+import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
+import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+
+export const RecordTableCellFieldInput = () => {
+ const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
+ useContext(RecordTableContext);
+ const { entityId, fieldDefinition } = useContext(FieldContext);
+ const { isReadOnly } = useContext(RecordTableRowContext);
+
+ const handleEnter: FieldInputEvent = (persistField) => {
+ onUpsertRecord({
+ persistField,
+ entityId,
+ fieldName: fieldDefinition.metadata.fieldName,
+ });
+
+ onCloseTableCell();
+ onMoveFocus('down');
+ };
+
+ const handleSubmit: FieldInputEvent = (persistField) => {
+ onUpsertRecord({
+ persistField,
+ entityId,
+ fieldName: fieldDefinition.metadata.fieldName,
+ });
+
+ onCloseTableCell();
+ };
+
+ const handleCancel = () => {
+ onCloseTableCell();
+ };
+
+ const handleClickOutside: FieldInputEvent = (persistField) => {
+ onUpsertRecord({
+ persistField,
+ entityId,
+ fieldName: fieldDefinition.metadata.fieldName,
+ });
+
+ onCloseTableCell();
+ };
+
+ const handleEscape: FieldInputEvent = (persistField) => {
+ onUpsertRecord({
+ persistField,
+ entityId,
+ fieldName: fieldDefinition.metadata.fieldName,
+ });
+
+ onCloseTableCell();
+ };
+
+ const handleTab: FieldInputEvent = (persistField) => {
+ onUpsertRecord({
+ persistField,
+ entityId,
+ fieldName: fieldDefinition.metadata.fieldName,
+ });
+
+ onCloseTableCell();
+ onMoveFocus('right');
+ };
+
+ const handleShiftTab: FieldInputEvent = (persistField) => {
+ onUpsertRecord({
+ persistField,
+ entityId,
+ fieldName: fieldDefinition.metadata.fieldName,
+ });
+
+ onCloseTableCell();
+ onMoveFocus('left');
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellGrip.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellGrip.tsx
new file mode 100644
index 000000000000..563646604447
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellGrip.tsx
@@ -0,0 +1,44 @@
+import styled from '@emotion/styled';
+import { useContext } from 'react';
+
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
+import { IconListViewGrip } from '@/ui/input/components/IconListViewGrip';
+
+const StyledContainer = styled.div`
+ cursor: grab;
+ width: 16px;
+ height: 32px;
+ z-index: 200;
+ display: flex;
+ &:hover .icon {
+ opacity: 1;
+ }
+
+ border-color: transparent;
+`;
+
+const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
+ opacity: ${({ isDragging }) => (isDragging ? 1 : 0)};
+ transition: opacity 0.1s;
+`;
+
+export const RecordTableCellGrip = () => {
+ const { dragHandleProps, isDragging } = useContext(RecordTableRowContext);
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellLoading.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellLoading.tsx
index 12050e5b6740..a8ca441b60e7 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellLoading.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellLoading.tsx
@@ -1,10 +1,10 @@
-import { StyledTd } from '@/object-record/record-table/components/RecordTableRow';
import { RecordTableCellSkeletonLoader } from '@/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader';
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
export const RecordTableCellLoading = () => {
return (
-
+
-
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellWrapper.tsx
new file mode 100644
index 000000000000..c552fa47beb6
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellWrapper.tsx
@@ -0,0 +1,75 @@
+import { useContext, useMemo } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
+import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
+import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
+import { isSoftFocusOnTableCellComponentFamilyState } from '@/object-record/record-table/states/isSoftFocusOnTableCellComponentFamilyState';
+import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
+import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
+import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
+import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
+import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
+import { extractComponentFamilyState } from '@/ui/utilities/state/component-state/utils/extractComponentFamilyState';
+
+export const RecordTableCellWrapper = ({
+ children,
+ column,
+ columnIndex,
+}: {
+ column: ColumnDefinition;
+ columnIndex: number;
+ children: React.ReactNode;
+}) => {
+ const tableScopeId = useAvailableScopeIdOrThrow(
+ RecordTableScopeInternalContext,
+ getScopeIdOrUndefinedFromComponentId(),
+ );
+
+ const { rowIndex } = useContext(RecordTableRowContext);
+
+ const currentTableCellPosition: TableCellPosition = useMemo(
+ () => ({
+ column: columnIndex,
+ row: rowIndex,
+ }),
+ [columnIndex, rowIndex],
+ );
+
+ const isTableCellInEditModeFamilyState = extractComponentFamilyState(
+ isTableCellInEditModeComponentFamilyState,
+ tableScopeId,
+ );
+
+ const isSoftFocusOnTableCellFamilyState = extractComponentFamilyState(
+ isSoftFocusOnTableCellComponentFamilyState,
+ tableScopeId,
+ );
+
+ const isInEditMode = useRecoilValue(
+ isTableCellInEditModeFamilyState(currentTableCellPosition),
+ );
+
+ const hasSoftFocus = useRecoilValue(
+ isSoftFocusOnTableCellFamilyState(currentTableCellPosition),
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell.tsx
new file mode 100644
index 000000000000..c8f5bccdfed7
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell.tsx
@@ -0,0 +1,10 @@
+import { useContext } from 'react';
+
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
+
+export const RecordTableLastEmptyCell = () => {
+ const { isSelected } = useContext(RecordTableRowContext);
+
+ return ;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx
new file mode 100644
index 000000000000..49f51197db11
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableTd.tsx
@@ -0,0 +1,102 @@
+import { DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
+import { styled } from '@linaria/react';
+import { ReactNode, useContext } from 'react';
+import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui';
+
+import { isDefined } from '~/utils/isDefined';
+
+const StyledTd = styled.td<{
+ zIndex?: number;
+ backgroundColor: string;
+ borderColor: string;
+ isDragging?: boolean;
+ fontColor: string;
+ sticky?: boolean;
+ freezeFirstColumns?: boolean;
+ left?: number;
+ hasRightBorder?: boolean;
+ hasBottomBorder?: boolean;
+}>`
+ border-bottom: 1px solid
+ ${({ borderColor, hasBottomBorder }) =>
+ hasBottomBorder ? borderColor : 'transparent'};
+ color: ${({ fontColor }) => fontColor};
+ border-right: 1px solid
+ ${({ borderColor, hasRightBorder }) =>
+ hasRightBorder ? borderColor : 'transparent'};
+
+ padding: 0;
+
+ text-align: left;
+
+ background: ${({ backgroundColor }) => backgroundColor};
+ z-index: ${({ zIndex }) => (isDefined(zIndex) ? zIndex : 'auto')};
+
+ ${({ isDragging }) =>
+ isDragging
+ ? `
+ background-color: transparent;
+ border-color: transparent;
+ `
+ : ''}
+
+ ${({ freezeFirstColumns }) =>
+ freezeFirstColumns
+ ? `@media (max-width: ${MOBILE_VIEWPORT}px) {
+ width: 35px;
+ max-width: 35px;
+ }`
+ : ''}
+`;
+
+export const RecordTableTd = ({
+ children,
+ zIndex,
+ isSelected,
+ isDragging,
+ sticky,
+ freezeFirstColumns,
+ left,
+ hasRightBorder = true,
+ hasBottomBorder = true,
+ ...dragHandleProps
+}: {
+ className?: string;
+ children?: ReactNode;
+ zIndex?: number;
+ isSelected?: boolean;
+ isDragging?: boolean;
+ sticky?: boolean;
+ freezeFirstColumns?: boolean;
+ hasRightBorder?: boolean;
+ hasBottomBorder?: boolean;
+ left?: number;
+} & (Partial | null)) => {
+ const { theme } = useContext(ThemeContext);
+
+ const tdBackgroundColor = isSelected
+ ? theme.accent.quaternary
+ : theme.background.primary;
+
+ const borderColor = theme.border.color.light;
+ const fontColor = theme.font.color.primary;
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts
index 792b33adc009..9aad45bc4e26 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts
@@ -1,6 +1,5 @@
-import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
+import { RecordTableCellContextProps } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContextProps } from '@/object-record/record-table/contexts/RecordTableRowContext';
-import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const recordTableRow: RecordTableRowContextProps = {
@@ -10,12 +9,13 @@ export const recordTableRow: RecordTableRowContextProps = {
pathToShowPage: '/',
objectNameSingular: 'objectNameSingular',
isReadOnly: false,
+ dragHandleProps: {} as any,
+ isDragging: false,
+ inView: true,
+ isPendingRow: false,
};
-export const recordTableCell: {
- columnDefinition: ColumnDefinition;
- columnIndex: number;
-} = {
+export const recordTableCell:RecordTableCellContextProps= {
columnIndex: 3,
columnDefinition: {
size: 1,
@@ -29,4 +29,10 @@ export const recordTableCell: {
fieldName: 'fieldName',
},
},
+ cellPosition: {
+ row: 2,
+ column: 3,
+ },
+ hasSoftFocus: false,
+ isInEditMode: false,
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition.ts
index d3384909cf9d..383a81de4050 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition.ts
@@ -1,21 +1,9 @@
-import { useContext, useMemo } from 'react';
+import { useContext } from 'react';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
-import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
-
-import { TableCellPosition } from '../../types/TableCellPosition';
export const useCurrentTableCellPosition = () => {
- const { rowIndex } = useContext(RecordTableRowContext);
- const { columnIndex } = useContext(RecordTableCellContext);
-
- const currentTableCellPosition: TableCellPosition = useMemo(
- () => ({
- column: columnIndex,
- row: rowIndex,
- }),
- [columnIndex, rowIndex],
- );
+ const { cellPosition } = useContext(RecordTableCellContext);
- return currentTableCellPosition;
+ return cellPosition;
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/ColumnHead.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx
similarity index 67%
rename from packages/twenty-front/src/modules/object-record/record-table/components/ColumnHead.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx
index 89caf5a48fc3..7a5c9f3cd6f7 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/ColumnHead.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx
@@ -1,14 +1,14 @@
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
-import { useRecoilValue } from 'recoil';
import { MOBILE_VIEWPORT, useIcons } from 'twenty-ui';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
-import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
+import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
-import { ColumnDefinition } from '../types/ColumnDefinition';
+import { ColumnDefinition } from '../../types/ColumnDefinition';
-type ColumnHeadProps = {
+type RecordTableColumnHeadProps = {
column: ColumnDefinition;
};
@@ -46,16 +46,22 @@ const StyledText = styled.span`
white-space: nowrap;
`;
-export const ColumnHead = ({ column }: ColumnHeadProps) => {
+export const RecordTableColumnHead = ({
+ column,
+}: RecordTableColumnHeadProps) => {
const theme = useTheme();
const { getIcon } = useIcons();
const Icon = getIcon(column.iconName);
- const scrollLeft = useRecoilValue(scrollLeftState);
+ const isRecordTableScrolledLeft = useRecoilComponentValue(
+ isRecordTableScrolledLeftComponentState,
+ );
return (
- 0}>
+
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableColumnDropdownMenu.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
similarity index 92%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RecordTableColumnDropdownMenu.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
index bbd2f131c5ed..6065e45a1198 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableColumnDropdownMenu.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
@@ -14,16 +14,16 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
-import { useTableColumns } from '../hooks/useTableColumns';
-import { ColumnDefinition } from '../types/ColumnDefinition';
+import { useTableColumns } from '../../hooks/useTableColumns';
+import { ColumnDefinition } from '../../types/ColumnDefinition';
-export type RecordTableColumnDropdownMenuProps = {
+export type RecordTableColumnHeadDropdownMenuProps = {
column: ColumnDefinition;
};
-export const RecordTableColumnDropdownMenu = ({
+export const RecordTableColumnHeadDropdownMenu = ({
column,
-}: RecordTableColumnDropdownMenuProps) => {
+}: RecordTableColumnHeadDropdownMenuProps) => {
const {
visibleTableColumnsSelector,
onToggleColumnFilterState,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/ColumnHeadWithDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx
similarity index 56%
rename from packages/twenty-front/src/modules/object-record/record-table/components/ColumnHeadWithDropdown.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx
index 475fb4beba60..d1ad7e1de48b 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/ColumnHeadWithDropdown.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx
@@ -4,26 +4,29 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
-import { ColumnHead } from './ColumnHead';
-import { RecordTableColumnDropdownMenu } from './RecordTableColumnDropdownMenu';
+import { RecordTableColumnHeadDropdownMenu } from './RecordTableColumnHeadDropdownMenu';
-type ColumnHeadWithDropdownProps = {
+import { RecordTableColumnHead } from './RecordTableColumnHead';
+
+type RecordTableColumnHeadWithDropdownProps = {
column: ColumnDefinition;
};
const StyledDropdown = styled(Dropdown)`
display: flex;
+
flex: 1;
+ z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
-export const ColumnHeadWithDropdown = ({
+export const RecordTableColumnHeadWithDropdown = ({
column,
-}: ColumnHeadWithDropdownProps) => {
+}: RecordTableColumnHeadWithDropdownProps) => {
return (
}
- dropdownComponents={}
+ clickableComponent={}
+ dropdownComponents={}
dropdownOffset={{ x: -1 }}
dropdownPlacement="bottom-start"
dropdownHotkeyScope={{ scope: column.fieldMetadataId + '-header' }}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
new file mode 100644
index 000000000000..49faaf868a27
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
@@ -0,0 +1,91 @@
+import styled from '@emotion/styled';
+import { useRecoilValue } from 'recoil';
+import { MOBILE_VIEWPORT } from 'twenty-ui';
+
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableHeaderCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCell';
+import { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn';
+import { RecordTableHeaderDragDropColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn';
+import { RecordTableHeaderLastColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn';
+
+const StyledTableHead = styled.thead<{
+ isScrolledTop?: boolean;
+ isScrolledLeft?: boolean;
+}>`
+ cursor: pointer;
+
+ th:nth-of-type(1) {
+ width: 9px;
+ left: 0;
+ border-right-color: ${({ theme }) => theme.background.primary};
+ }
+
+ th:nth-of-type(2) {
+ border-right-color: ${({ theme }) => theme.background.primary};
+ }
+
+ &.first-columns-sticky {
+ th:nth-child(1) {
+ position: sticky;
+ left: 0;
+ z-index: 5;
+ }
+ th:nth-child(2) {
+ position: sticky;
+ left: 9px;
+ z-index: 5;
+ }
+ th:nth-child(3) {
+ position: sticky;
+ left: 39px;
+ z-index: 5;
+ @media (max-width: ${MOBILE_VIEWPORT}px) {
+ width: 35px;
+ max-width: 35px;
+ }
+ }
+ }
+
+ &.header-sticky {
+ th {
+ position: sticky;
+ top: 0;
+ z-index: 5;
+ }
+ }
+
+ &.header-sticky.first-columns-sticky {
+ th:nth-child(1),
+ th:nth-child(2),
+ th:nth-child(3) {
+ z-index: 10;
+ }
+ }
+`;
+
+export const RecordTableHeader = ({
+ createRecord,
+}: {
+ createRecord: () => void;
+}) => {
+ const { visibleTableColumnsSelector } = useRecordTableStates();
+
+ const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
+
+ return (
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx
similarity index 85%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderCell.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx
index 702b644ffc37..d243b2a29b6e 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderCell.tsx
@@ -1,27 +1,35 @@
-import { useCallback, useMemo, useState } from 'react';
import styled from '@emotion/styled';
+import { useCallback, useMemo, useState } from 'react';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { IconPlus } from 'twenty-ui';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
+import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
+import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
-import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
-import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown';
-
const COLUMN_MIN_WIDTH = 104;
const StyledColumnHeaderCell = styled.th<{
columnWidth: number;
isResizing?: boolean;
}>`
+ border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
+ border-top: 1px solid ${({ theme }) => theme.border.color.light};
+ color: ${({ theme }) => theme.font.color.tertiary};
+ padding: 0;
+ text-align: left;
+
+ background-color: ${({ theme }) => theme.background.primary};
+ border-right: 1px solid ${({ theme }) => theme.border.color.light};
${({ columnWidth }) => `
min-width: ${columnWidth}px;
width: ${columnWidth}px;
@@ -165,11 +173,14 @@ export const RecordTableHeaderCell = ({
onMouseUp: handleResizeHandlerEnd,
});
+ const isRecordTableScrolledLeft = useRecoilComponentValue(
+ isRecordTableScrolledLeftComponentState,
+ );
+
const isMobile = useIsMobile();
- const scrollLeft = useRecoilValue(scrollLeftState);
const disableColumnResize =
- column.isLabelIdentifier && isMobile && scrollLeft > 0;
+ column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
return (
setIconVisibility(false)}
>
-
+
{(useIsMobile() || iconVisibility) && !!column.isLabelIdentifier && (
theme.background.primary};
`;
-export const SelectAllCheckbox = () => {
+export const RecordTableHeaderCheckboxColumn = () => {
const { allRowsSelectedStatusSelector } = useRecordTableStates();
const allRowsSelectedStatus = useRecoilValue(allRowsSelectedStatusSelector());
@@ -36,13 +36,26 @@ export const SelectAllCheckbox = () => {
}
};
+ const theme = useTheme();
+
return (
-
-
-
+
+
+
+
+ |
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn.tsx
new file mode 100644
index 000000000000..3acf7afa9ad1
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn.tsx
@@ -0,0 +1,10 @@
+import { styled } from '@linaria/react';
+
+const StyledTh = styled.th`
+ border-bottom: none;
+ border-top: none;
+`;
+
+export const RecordTableHeaderDragDropColumn = () => {
+ return ;
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx
new file mode 100644
index 000000000000..170ec63f74b9
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn.tsx
@@ -0,0 +1,89 @@
+import { Theme } from '@emotion/react';
+import styled from '@emotion/styled';
+import { useContext } from 'react';
+import { useRecoilValue } from 'recoil';
+import { IconPlus, ThemeContext } from 'twenty-ui';
+
+import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId';
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
+import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
+import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
+
+const StyledPlusIconHeaderCell = styled.th<{
+ theme: Theme;
+ isTableWiderThanScreen: boolean;
+}>`
+ ${({ theme }) => {
+ return `
+ &:hover {
+ background: ${theme.background.transparent.light};
+ };
+ padding-left: ${theme.spacing(3)};
+ `;
+ }};
+ border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
+ border-top: 1px solid ${({ theme }) => theme.border.color.light};
+ background-color: ${({ theme }) => theme.background.primary};
+ border-left: none !important;
+ color: ${({ theme }) => theme.font.color.tertiary};
+ min-width: 32px;
+ border-right: none !important;
+
+ ${({ isTableWiderThanScreen, theme }) =>
+ isTableWiderThanScreen
+ ? `
+ width: 32px;
+ background-color: ${theme.background.primary};
+ `
+ : ''};
+ z-index: 1;
+`;
+
+const StyledPlusIconContainer = styled.div`
+ align-items: center;
+ display: flex;
+ height: 32px;
+ justify-content: center;
+ width: 32px;
+`;
+
+const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
+ 'hidden-table-columns-dropdown-hotkey-scope-id';
+
+export const RecordTableHeaderLastColumn = () => {
+ const { theme } = useContext(ThemeContext);
+
+ const scrollWrapper = useScrollWrapperScopedRef();
+
+ const isTableWiderThanScreen =
+ (scrollWrapper.current?.clientWidth ?? 0) <
+ (scrollWrapper.current?.scrollWidth ?? 0);
+
+ const { hiddenTableColumnsSelector } = useRecordTableStates();
+
+ const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector());
+
+ return (
+
+ {hiddenTableColumns.length > 0 && (
+
+
+
+ }
+ dropdownComponents={}
+ dropdownPlacement="bottom-start"
+ dropdownHotkeyScope={{
+ scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
+ }}
+ />
+ )}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderPlusButtonContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent.tsx
similarity index 100%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RecordTableHeaderPlusButtonContent.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent.tsx
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCells.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCells.tsx
new file mode 100644
index 000000000000..25c557144d9f
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCells.tsx
@@ -0,0 +1,17 @@
+import { useContext } from 'react';
+
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { RecordTableCellsEmpty } from '@/object-record/record-table/record-table-row/components/RecordTableCellsEmpty';
+import { RecordTableCellsVisible } from '@/object-record/record-table/record-table-row/components/RecordTableCellsVisible';
+
+export const RecordTableCells = () => {
+ const { inView, isDragging } = useContext(RecordTableRowContext);
+
+ const areCellsVisible = inView || isDragging;
+
+ return areCellsVisible ? (
+
+ ) : (
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsEmpty.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsEmpty.tsx
new file mode 100644
index 000000000000..2df7a27f4a5a
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsEmpty.tsx
@@ -0,0 +1,17 @@
+import { useContext } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+
+export const RecordTableCellsEmpty = () => {
+ const { isSelected } = useContext(RecordTableRowContext);
+ const { visibleTableColumnsSelector } = useRecordTableStates();
+
+ const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
+
+ return visibleTableColumns.map((column) => (
+
+ ));
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsVisible.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsVisible.tsx
new file mode 100644
index 000000000000..e6b75b265074
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableCellsVisible.tsx
@@ -0,0 +1,39 @@
+import { useContext } from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCell';
+import { RecordTableCellWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellWrapper';
+import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
+
+export const RecordTableCellsVisible = () => {
+ const { isDragging } = useContext(RecordTableRowContext);
+ const { visibleTableColumnsSelector } = useRecordTableStates();
+
+ const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());
+
+ const tableColumnsAfterFirst = visibleTableColumns.slice(1);
+
+ return (
+ <>
+
+
+
+
+
+ {!isDragging &&
+ tableColumnsAfterFirst.map((column, columnIndex) => (
+
+
+
+
+
+ ))}
+ >
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTablePendingRow.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRow.tsx
similarity index 76%
rename from packages/twenty-front/src/modules/object-record/record-table/components/RecordTablePendingRow.tsx
rename to packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRow.tsx
index cde96d52e1a3..addf2d747b8a 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTablePendingRow.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTablePendingRow.tsx
@@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil';
-import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
export const RecordTablePendingRow = () => {
const { pendingRecordIdState } = useRecordTableStates();
const pendingRecordId = useRecoilValue(pendingRecordIdState);
- if (!pendingRecordId) return;
+ if (!pendingRecordId) return <>>;
return (
{
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowWrapper.tsx
new file mode 100644
index 000000000000..d64f316e12bd
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableRowWrapper.tsx
@@ -0,0 +1,87 @@
+import { ReactNode, useContext } from 'react';
+import { useInView } from 'react-intersection-observer';
+import { useTheme } from '@emotion/react';
+import { Draggable } from '@hello-pangea/dnd';
+import { useRecoilValue } from 'recoil';
+
+import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
+import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
+import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
+import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
+import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
+import { ScrollWrapperContext } from '@/ui/utilities/scroll/components/ScrollWrapper';
+
+export const RecordTableRowWrapper = ({
+ recordId,
+ rowIndex,
+ isPendingRow,
+ children,
+}: {
+ recordId: string;
+ rowIndex: number;
+ isPendingRow?: boolean;
+ children: ReactNode;
+}) => {
+ const { objectMetadataItem } = useContext(RecordTableContext);
+
+ const theme = useTheme();
+
+ const { isRowSelectedFamilyState } = useRecordTableStates();
+ const currentRowSelected = useRecoilValue(isRowSelectedFamilyState(recordId));
+
+ const scrollWrapperRef = useContext(ScrollWrapperContext);
+
+ const { ref: elementRef, inView } = useInView({
+ root: scrollWrapperRef.current?.querySelector(
+ '[data-overlayscrollbars-viewport="scrollbarHidden"]',
+ ),
+ rootMargin: '1000px',
+ });
+
+ return (
+
+ {(draggableProvided, draggableSnapshot) => (
+ {
+ elementRef(node);
+ draggableProvided.innerRef(node);
+ }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...draggableProvided.draggableProps}
+ style={{
+ ...draggableProvided.draggableProps.style,
+ background: draggableSnapshot.isDragging
+ ? theme.background.transparent.light
+ : 'none',
+ borderColor: draggableSnapshot.isDragging
+ ? `${theme.border.color.medium}`
+ : 'transparent',
+ }}
+ isDragging={draggableSnapshot.isDragging}
+ data-testid={`row-id-${recordId}`}
+ data-selectable-id={recordId}
+ >
+
+ {children}
+
+
+ )}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx
new file mode 100644
index 000000000000..0ccfc98ac751
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx
@@ -0,0 +1,11 @@
+import styled from '@emotion/styled';
+
+const StyledTr = styled.tr<{ isDragging: boolean }>`
+ border: ${({ isDragging, theme }) =>
+ isDragging
+ ? `1px solid ${theme.border.color.medium}`
+ : '1px solid transparent'};
+ transition: border-left-color 0.2s ease-in-out;
+`;
+
+export const RecordTableTr = StyledTr;
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledLeftComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledLeftComponentState.ts
new file mode 100644
index 000000000000..6f26d8a6082a
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledLeftComponentState.ts
@@ -0,0 +1,9 @@
+import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+
+export const isRecordTableScrolledLeftComponentState =
+ createComponentStateV2({
+ key: 'isRecordTableScrolledLeftComponentState',
+ componentContext: RecordTableScopeInternalContext,
+ defaultValue: true,
+ });
diff --git a/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledTopComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledTopComponentState.ts
new file mode 100644
index 000000000000..564c567a6062
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/states/isRecordTableScrolledTopComponentState.ts
@@ -0,0 +1,9 @@
+import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
+import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
+
+export const isRecordTableScrolledTopComponentState =
+ createComponentStateV2({
+ key: 'isRecordTableScrolledTopComponentState',
+ componentContext: RecordTableScopeInternalContext,
+ defaultValue: true,
+ });
diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx
index 5964f92cc798..a7ba44db239c 100644
--- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx
+++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/Dropdown.tsx
@@ -1,12 +1,13 @@
-import { useRef } from 'react';
-import { Keys } from 'react-hotkeys-hook';
import {
autoUpdate,
flip,
+ FloatingPortal,
offset,
Placement,
useFloating,
} from '@floating-ui/react';
+import { useRef } from 'react';
+import { Keys } from 'react-hotkeys-hook';
import { Key } from 'ts-key-enum';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
@@ -85,7 +86,7 @@ export const Dropdown = ({
};
useListenClickOutside({
- refs: [containerRef],
+ refs: [refs.floating],
callback: () => {
onClickOutside?.();
@@ -131,15 +132,17 @@ export const Dropdown = ({
/>
)}
{isDropdownOpen && (
-
- {dropdownComponents}
-
+
+
+ {dropdownComponents}
+
+
)}
(
-
{children}
-
+
);
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useRecoilComponentValue.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useRecoilComponentValue.ts
new file mode 100644
index 000000000000..d72ee3cae3f2
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useRecoilComponentValue.ts
@@ -0,0 +1,29 @@
+import { useRecoilValue } from 'recoil';
+
+import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
+import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
+import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
+
+export const useRecoilComponentValue = (
+ componentState: ComponentState,
+ componentId?: string,
+) => {
+ const componentContext = (window as any).componentContextStateMap?.get(
+ componentState.key,
+ );
+
+ if (!componentContext) {
+ throw new Error(
+ `Component context for key "${componentState.key}" is not defined`,
+ );
+ }
+
+ const internalComponentId = useAvailableScopeIdOrThrow(
+ componentContext,
+ getScopeIdOrUndefinedFromComponentId(componentId),
+ );
+
+ return useRecoilValue(
+ componentState.atomFamily({ scopeId: internalComponentId }),
+ );
+};
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useSetRecoilComponentState.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useSetRecoilComponentState.ts
new file mode 100644
index 000000000000..938d699b3f51
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/hooks/useSetRecoilComponentState.ts
@@ -0,0 +1,29 @@
+import { useSetRecoilState } from 'recoil';
+
+import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
+import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
+import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
+
+export const useSetRecoilComponentState = (
+ componentState: ComponentState,
+ componentId?: string,
+) => {
+ const componentContext = (window as any).componentContextStateMap?.get(
+ componentState.key,
+ );
+
+ if (!componentContext) {
+ throw new Error(
+ `Component context for key "${componentState.key}" is not defined`,
+ );
+ }
+
+ const internalComponentId = useAvailableScopeIdOrThrow(
+ componentContext,
+ getScopeIdOrUndefinedFromComponentId(componentId),
+ );
+
+ return useSetRecoilState(
+ componentState.atomFamily({ scopeId: internalComponentId }),
+ );
+};
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/types/ComponentState.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/types/ComponentState.ts
new file mode 100644
index 000000000000..8fc43e8a291b
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/types/ComponentState.ts
@@ -0,0 +1,8 @@
+import { RecoilState } from 'recoil';
+
+import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
+
+export type ComponentState = {
+ key: string;
+ atomFamily: (componentStateKey: ComponentStateKey) => RecoilState;
+};
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentState.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentState.ts
index b72932d8627d..ffbd9dd7eb48 100644
--- a/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentState.ts
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentState.ts
@@ -2,15 +2,17 @@ import { AtomEffect, atomFamily } from 'recoil';
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
+type CreateComponentStateType = {
+ key: string;
+ defaultValue: ValueType;
+ effects?: AtomEffect[];
+};
+
export const createComponentState = ({
key,
defaultValue,
effects,
-}: {
- key: string;
- defaultValue: ValueType;
- effects?: AtomEffect[];
-}) => {
+}: CreateComponentStateType) => {
return atomFamily({
key,
default: defaultValue,
diff --git a/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentStateV2.ts b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentStateV2.ts
new file mode 100644
index 000000000000..1fda1db8210e
--- /dev/null
+++ b/packages/twenty-front/src/modules/ui/utilities/state/component-state/utils/createComponentStateV2.ts
@@ -0,0 +1,37 @@
+import { AtomEffect, atomFamily } from 'recoil';
+
+import { ScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopeInternalContext';
+import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
+import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
+import { isDefined } from '~/utils/isDefined';
+
+type CreateComponentStateV2Type = {
+ key: string;
+ defaultValue: ValueType;
+ componentContext?: ScopeInternalContext | null;
+ effects?: AtomEffect[];
+};
+
+export const createComponentStateV2 = ({
+ key,
+ defaultValue,
+ componentContext,
+ effects,
+}: CreateComponentStateV2Type): ComponentState => {
+ if (isDefined(componentContext)) {
+ if (!isDefined((window as any).componentContextStateMap)) {
+ (window as any).componentContextStateMap = new Map();
+ }
+
+ (window as any).componentContextStateMap.set(key, componentContext);
+ }
+
+ return {
+ key,
+ atomFamily: atomFamily({
+ key,
+ default: defaultValue,
+ effects: effects,
+ }),
+ };
+};
diff --git a/packages/twenty-front/vite.config.ts b/packages/twenty-front/vite.config.ts
index 08534bf0a13d..705c2dcc51f5 100644
--- a/packages/twenty-front/vite.config.ts
+++ b/packages/twenty-front/vite.config.ts
@@ -67,6 +67,11 @@ export default defineConfig(({ command, mode }) => {
'**/RecordTableCellContainer.tsx',
'**/RecordTableCellDisplayContainer.tsx',
'**/Avatar.tsx',
+ '**/RecordTableBodyDroppable.tsx',
+ '**/RecordTableCellBaseContainer.tsx',
+ '**/RecordTableCellTd.tsx',
+ '**/RecordTableTd.tsx',
+ '**/RecordTableHeaderDragDropColumn.tsx',
],
babelOptions: {
presets: ['@babel/preset-typescript', '@babel/preset-react'],