Refacto scroll + Aggregate queries for view groups (#9089)
Closes https://github.com/twentyhq/private-issues/issues/217. Refactoring scroll not to cause table-wide re-render when opening a dropdown (triggering a scroll lock) in the table.
This commit is contained in:
@ -1,9 +1,10 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation, useNavigation } from 'react-router-dom';
|
import { useNavigation } from 'react-router-dom';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
import { overlayScrollbarsState } from '@/ui/utilities/scroll/states/overlayScrollbarsState';
|
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||||
import { scrollPositionState } from '@/ui/utilities/scroll/states/scrollPositionState';
|
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -13,23 +14,24 @@ import { isDefined } from '~/utils/isDefined';
|
|||||||
* not share the same scroll position.
|
* not share the same scroll position.
|
||||||
*/
|
*/
|
||||||
export const useScrollRestoration = (viewportHeight?: number) => {
|
export const useScrollRestoration = (viewportHeight?: number) => {
|
||||||
const key = `scroll-position-${useLocation().key}`;
|
|
||||||
const { state } = useNavigation();
|
const { state } = useNavigation();
|
||||||
|
|
||||||
const [scrollPosition, setScrollPosition] = useRecoilState(
|
const [scrollTop, setScrollTop] = useRecoilComponentStateV2(
|
||||||
scrollPositionState(key),
|
scrollWrapperScrollTopComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const overlayScrollbars = useRecoilValue(overlayScrollbarsState);
|
const overlayScrollbars = useRecoilComponentValueV2(
|
||||||
|
scrollWrapperInstanceComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const scrollWrapper = overlayScrollbars?.elements().viewport;
|
const scrollWrapper = overlayScrollbars?.elements().viewport;
|
||||||
const skip = isDefined(viewportHeight) && scrollPosition > viewportHeight;
|
const skip = isDefined(viewportHeight) && scrollTop > viewportHeight;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state === 'loading') {
|
if (state === 'loading') {
|
||||||
setScrollPosition(scrollWrapper?.scrollTop ?? 0);
|
setScrollTop(scrollWrapper?.scrollTop ?? 0);
|
||||||
} else if (state === 'idle' && isDefined(scrollWrapper) && !skip) {
|
} else if (state === 'idle' && isDefined(scrollWrapper) && !skip) {
|
||||||
scrollWrapper.scrollTo({ top: scrollPosition });
|
scrollWrapper.scrollTo({ top: scrollTop });
|
||||||
}
|
}
|
||||||
}, [key, state, scrollWrapper, skip, scrollPosition, setScrollPosition]);
|
}, [state, scrollWrapper, skip, scrollTop, setScrollTop]);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,19 +1,24 @@
|
|||||||
import { overlayScrollbarsState } from '@/ui/utilities/scroll/states/overlayScrollbarsState';
|
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||||
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
export const useScrollToPosition = () => {
|
export const useScrollToPosition = () => {
|
||||||
|
const scrollWrapperInstanceState = useRecoilComponentCallbackStateV2(
|
||||||
|
scrollWrapperInstanceComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const scrollToPosition = useRecoilCallback(
|
const scrollToPosition = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
(scrollPositionInPx: number) => {
|
(scrollPositionInPx: number) => {
|
||||||
const overlayScrollbars = snapshot
|
const overlayScrollbars = snapshot
|
||||||
.getLoadable(overlayScrollbarsState)
|
.getLoadable(scrollWrapperInstanceState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
const scrollWrapper = overlayScrollbars?.elements().viewport;
|
const scrollWrapper = overlayScrollbars?.elements().viewport;
|
||||||
|
|
||||||
scrollWrapper?.scrollTo({ top: scrollPositionInPx });
|
scrollWrapper?.scrollTo({ top: scrollPositionInPx });
|
||||||
},
|
},
|
||||||
[],
|
[scrollWrapperInstanceState],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { scrollToPosition };
|
return { scrollToPosition };
|
||||||
|
|||||||
@ -44,7 +44,10 @@ export const EventList = ({ events, targetableObject }: EventListProps) => {
|
|||||||
const groupedEvents = groupEventsByMonth(filteredEvents);
|
const groupedEvents = groupEventsByMonth(filteredEvents);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollWrapper contextProviderName="eventList">
|
<ScrollWrapper
|
||||||
|
contextProviderName="eventList"
|
||||||
|
componentInstanceId={`scroll-wrapper-event-list-${targetableObject.id}`}
|
||||||
|
>
|
||||||
<StyledTimelineContainer>
|
<StyledTimelineContainer>
|
||||||
{groupedEvents.map((group, index) => (
|
{groupedEvents.map((group, index) => (
|
||||||
<EventsGroup
|
<EventsGroup
|
||||||
|
|||||||
@ -610,7 +610,10 @@ export const CommandMenu = () => {
|
|||||||
setCommandMenuSearch={setCommandMenuSearch}
|
setCommandMenuSearch={setCommandMenuSearch}
|
||||||
/>
|
/>
|
||||||
<StyledList>
|
<StyledList>
|
||||||
<ScrollWrapper contextProviderName="commandMenu">
|
<ScrollWrapper
|
||||||
|
contextProviderName="commandMenu"
|
||||||
|
componentInstanceId={`scroll-wrapper-command-menu`}
|
||||||
|
>
|
||||||
<StyledInnerList isMobile={isMobile}>
|
<StyledInnerList isMobile={isMobile}>
|
||||||
<SelectableList
|
<SelectableList
|
||||||
selectableListId="command-menu-list"
|
selectableListId="command-menu-list"
|
||||||
|
|||||||
@ -58,7 +58,8 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
)}
|
)}
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="navigationDrawer"
|
contextProviderName="navigationDrawer"
|
||||||
enableXScroll={false}
|
componentInstanceId={`scroll-wrapper-navigation-drawer`}
|
||||||
|
defaultEnableXScroll={false}
|
||||||
scrollHide={true}
|
scrollHide={true}
|
||||||
>
|
>
|
||||||
<NavigationDrawerOpenedSection />
|
<NavigationDrawerOpenedSection />
|
||||||
|
|||||||
@ -194,7 +194,10 @@ export const RecordBoard = () => {
|
|||||||
<RecordBoardComponentInstanceContext.Provider
|
<RecordBoardComponentInstanceContext.Provider
|
||||||
value={{ instanceId: recordBoardId }}
|
value={{ instanceId: recordBoardId }}
|
||||||
>
|
>
|
||||||
<ScrollWrapper contextProviderName="recordBoard">
|
<ScrollWrapper
|
||||||
|
contextProviderName="recordBoard"
|
||||||
|
componentInstanceId={`scroll-wrapper-record-board-${recordBoardId}`}
|
||||||
|
>
|
||||||
<RecordBoardStickyHeaderEffect />
|
<RecordBoardStickyHeaderEffect />
|
||||||
<StyledContainerContainer>
|
<StyledContainerContainer>
|
||||||
<RecordBoardHeader />
|
<RecordBoardHeader />
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { useScrollTopValue } from '@/ui/utilities/scroll/hooks/useScrollTopValue';
|
|
||||||
|
|
||||||
export const RecordBoardStickyHeaderEffect = () => {
|
export const RecordBoardStickyHeaderEffect = () => {
|
||||||
const scrollTop = useScrollTopValue('recordBoard');
|
const scrollTop = useRecoilComponentValueV2(
|
||||||
|
scrollWrapperScrollTopComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: move this outside because it might cause way too many re-renders for other hooks
|
// TODO: move this outside because it might cause way too many re-renders for other hooks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { isDefined } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const useRecordGroupFilter = (fields: FieldMetadataItem[]) => {
|
||||||
|
const currentRecordGroupDefinition = useCurrentRecordGroupDefinition();
|
||||||
|
|
||||||
|
const recordGroupFilter = useMemo(() => {
|
||||||
|
if (isDefined(currentRecordGroupDefinition)) {
|
||||||
|
const fieldMetadataItem = fields.find(
|
||||||
|
(fieldMetadataItem) =>
|
||||||
|
fieldMetadataItem.id === currentRecordGroupDefinition.fieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fieldMetadataItem) {
|
||||||
|
throw new Error(
|
||||||
|
`Field metadata item with id ${currentRecordGroupDefinition.fieldMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDefined(currentRecordGroupDefinition.value)) {
|
||||||
|
return { [fieldMetadataItem.name]: { is: 'NULL' } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
[fieldMetadataItem.name]: {
|
||||||
|
eq: currentRecordGroupDefinition.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}, [currentRecordGroupDefinition, fields]);
|
||||||
|
|
||||||
|
return { recordGroupFilter };
|
||||||
|
};
|
||||||
@ -2,12 +2,11 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||||
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||||
|
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||||
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
|
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
|
||||||
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
|
import { tableSortsComponentState } from '@/object-record/record-table/states/tableSortsComponentState';
|
||||||
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
|
import { tableViewFilterGroupsComponentState } from '@/object-record/record-table/states/tableViewFilterGroupsComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { isDefined } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const useFindManyRecordIndexTableParams = (
|
export const useFindManyRecordIndexTableParams = (
|
||||||
objectNameSingular: string,
|
objectNameSingular: string,
|
||||||
@ -17,6 +16,10 @@ export const useFindManyRecordIndexTableParams = (
|
|||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { recordGroupFilter } = useRecordGroupFilter(
|
||||||
|
objectMetadataItem?.fields,
|
||||||
|
);
|
||||||
|
|
||||||
const currentRecordGroupDefinition = useCurrentRecordGroupDefinition();
|
const currentRecordGroupDefinition = useCurrentRecordGroupDefinition();
|
||||||
|
|
||||||
const tableViewFilterGroups = useRecoilComponentValueV2(
|
const tableViewFilterGroups = useRecoilComponentValueV2(
|
||||||
@ -38,33 +41,6 @@ export const useFindManyRecordIndexTableParams = (
|
|||||||
tableViewFilterGroups,
|
tableViewFilterGroups,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordGroupFilter = useMemo(() => {
|
|
||||||
if (isDefined(currentRecordGroupDefinition)) {
|
|
||||||
const fieldMetadataItem = objectMetadataItem?.fields.find(
|
|
||||||
(fieldMetadataItem) =>
|
|
||||||
fieldMetadataItem.id === currentRecordGroupDefinition.fieldMetadataId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!fieldMetadataItem) {
|
|
||||||
throw new Error(
|
|
||||||
`Field metadata item with id ${currentRecordGroupDefinition.fieldMetadataId} not found`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDefined(currentRecordGroupDefinition.value)) {
|
|
||||||
return { [fieldMetadataItem.name]: { is: 'NULL' } };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
[fieldMetadataItem.name]: {
|
|
||||||
eq: currentRecordGroupDefinition.value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}, [objectMetadataItem.fields, currentRecordGroupDefinition]);
|
|
||||||
|
|
||||||
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, tableSorts);
|
const orderBy = turnSortsIntoOrderBy(objectMetadataItem, tableSorts);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -96,7 +96,9 @@ export const RecordTable = () => {
|
|||||||
<RecordTableRecordGroupsBody />
|
<RecordTableRecordGroupsBody />
|
||||||
)}
|
)}
|
||||||
<RecordTableStickyEffect />
|
<RecordTableStickyEffect />
|
||||||
{isAggregateQueryEnabled && <RecordTableFooter />}
|
{isAggregateQueryEnabled && !hasRecordGroups && (
|
||||||
|
<RecordTableFooter />
|
||||||
|
)}
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
<DragSelect
|
<DragSelect
|
||||||
dragSelectable={tableBodyRef}
|
dragSelectable={tableBodyRef}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||||
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
|
||||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
|
import { RecordTableFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableFooter';
|
||||||
import { RecordTablePendingRecordGroupRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow';
|
import { RecordTablePendingRecordGroupRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow';
|
||||||
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
|
||||||
import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew';
|
import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew';
|
||||||
@ -8,10 +9,14 @@ import { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-ta
|
|||||||
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
||||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
export const RecordTableRecordGroupRows = () => {
|
export const RecordTableRecordGroupRows = () => {
|
||||||
|
const isAggregateQueryEnabled = useIsFeatureEnabled(
|
||||||
|
'IS_AGGREGATE_QUERY_ENABLED',
|
||||||
|
);
|
||||||
const currentRecordGroupId = useCurrentRecordGroupId();
|
const currentRecordGroupId = useCurrentRecordGroupId();
|
||||||
|
|
||||||
const allRecordIds = useRecoilComponentValueV2(
|
const allRecordIds = useRecoilComponentValueV2(
|
||||||
@ -57,8 +62,14 @@ export const RecordTableRecordGroupRows = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<RecordTablePendingRecordGroupRow />
|
<RecordTablePendingRecordGroupRow />
|
||||||
<RecordTableRecordGroupSectionLoadMore />
|
|
||||||
<RecordTableRecordGroupSectionAddNew />
|
<RecordTableRecordGroupSectionAddNew />
|
||||||
|
{isAggregateQueryEnabled && (
|
||||||
|
<RecordTableFooter
|
||||||
|
key={currentRecordGroupId}
|
||||||
|
currentRecordGroupId={currentRecordGroupId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<RecordTableRecordGroupSectionLoadMore />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||||
import { useScrollLeftValue } from '@/ui/utilities/scroll/hooks/useScrollLeftValue';
|
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||||
import { useScrollTopValue } from '@/ui/utilities/scroll/hooks/useScrollTopValue';
|
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
|
||||||
export const RecordTableStickyEffect = () => {
|
export const RecordTableStickyEffect = () => {
|
||||||
const scrollTop = useScrollTopValue('recordTableWithWrappers');
|
const scrollTop = useRecoilComponentValueV2(
|
||||||
|
scrollWrapperScrollTopComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrollTop > 0) {
|
if (scrollTop > 0) {
|
||||||
@ -20,7 +23,9 @@ export const RecordTableStickyEffect = () => {
|
|||||||
}
|
}
|
||||||
}, [scrollTop]);
|
}, [scrollTop]);
|
||||||
|
|
||||||
const scrollLeft = useScrollLeftValue('recordTableWithWrappers');
|
const scrollLeft = useRecoilComponentValueV2(
|
||||||
|
scrollWrapperScrollLeftComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const setIsRecordTableScrolledLeft = useSetRecoilComponentStateV2(
|
const setIsRecordTableScrolledLeft = useSetRecoilComponentStateV2(
|
||||||
isRecordTableScrolledLeftComponentState,
|
isRecordTableScrolledLeftComponentState,
|
||||||
|
|||||||
@ -10,8 +10,6 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
|||||||
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
||||||
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
||||||
|
|
||||||
import { isScrollEnabledForRecordTableState } from '@/object-record/record-table/states/isScrollEnabledForRecordTableState';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
||||||
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||||
import { useRecordTable } from '../hooks/useRecordTable';
|
import { useRecordTable } from '../hooks/useRecordTable';
|
||||||
|
|
||||||
@ -50,11 +48,6 @@ export const RecordTableWithWrappers = ({
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
viewBarId,
|
viewBarId,
|
||||||
}: RecordTableWithWrappersProps) => {
|
}: RecordTableWithWrappersProps) => {
|
||||||
const isScrollEnabledForRecordTable = useRecoilComponentValueV2(
|
|
||||||
isScrollEnabledForRecordTableState,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { resetTableRowSelection, selectAllRows, setHasUserSelectedAllRows } =
|
const { resetTableRowSelection, selectAllRows, setHasUserSelectedAllRows } =
|
||||||
useRecordTable({
|
useRecordTable({
|
||||||
recordTableId,
|
recordTableId,
|
||||||
@ -109,9 +102,8 @@ export const RecordTableWithWrappers = ({
|
|||||||
>
|
>
|
||||||
<EntityDeleteContext.Provider value={deleteOneRecord}>
|
<EntityDeleteContext.Provider value={deleteOneRecord}>
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
enableXScroll={isScrollEnabledForRecordTable.enableXScroll}
|
|
||||||
enableYScroll={isScrollEnabledForRecordTable.enableYScroll}
|
|
||||||
contextProviderName="recordTableWithWrappers"
|
contextProviderName="recordTableWithWrappers"
|
||||||
|
componentInstanceId={`record-table-scroll-${recordTableId}`}
|
||||||
>
|
>
|
||||||
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
||||||
<StyledTableWithHeader>
|
<StyledTableWithHeader>
|
||||||
|
|||||||
@ -53,6 +53,11 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const tableColumnsState = useRecoilComponentCallbackStateV2(
|
||||||
|
tableColumnsComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
const setAvailableTableColumns = useRecoilCallback(
|
const setAvailableTableColumns = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
(columns: ColumnDefinition<FieldMetadata>[]) => {
|
(columns: ColumnDefinition<FieldMetadata>[]) => {
|
||||||
@ -69,6 +74,19 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
[availableTableColumnsState],
|
[availableTableColumnsState],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setTableColumns = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
(columns: ColumnDefinition<FieldMetadata>[]) => {
|
||||||
|
const tableColumns = getSnapshotValue(snapshot, tableColumnsState);
|
||||||
|
|
||||||
|
if (isDeeplyEqual(tableColumns, columns)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set(tableColumnsState, columns);
|
||||||
|
},
|
||||||
|
[tableColumnsState],
|
||||||
|
);
|
||||||
|
|
||||||
const setOnEntityCountChange = useSetRecoilComponentStateV2(
|
const setOnEntityCountChange = useSetRecoilComponentStateV2(
|
||||||
onEntityCountChangeComponentState,
|
onEntityCountChangeComponentState,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
@ -89,11 +107,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const setTableColumns = useSetRecoilComponentStateV2(
|
|
||||||
tableColumnsComponentState,
|
|
||||||
recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setOnColumnsChange = useSetRecoilComponentStateV2(
|
const setOnColumnsChange = useSetRecoilComponentStateV2(
|
||||||
onColumnsChangeComponentState,
|
onColumnsChangeComponentState,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
|
|||||||
@ -6,13 +6,15 @@ import { RecordTableTd } from '@/object-record/record-table/record-table-cell/co
|
|||||||
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
||||||
import { Checkbox } from 'twenty-ui';
|
import { Checkbox } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const TABLE_CELL_CHECKBOX_MIN_WIDTH = '24px';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-width: 24px;
|
min-width: ${TABLE_CELL_CHECKBOX_MIN_WIDTH};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordTableCellCheckbox = () => {
|
export const RecordTableCellCheckbox = () => {
|
||||||
|
|||||||
@ -6,12 +6,14 @@ import { RecordTableTd } from '@/object-record/record-table/record-table-cell/co
|
|||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import { IconListViewGrip } from 'twenty-ui';
|
import { IconListViewGrip } from 'twenty-ui';
|
||||||
|
|
||||||
|
export const TABLE_CELL_GRIP_WIDTH = '16px';
|
||||||
|
|
||||||
const StyledContainer = styled.div<{ isPendingRow?: boolean }>`
|
const StyledContainer = styled.div<{ isPendingRow?: boolean }>`
|
||||||
|
height: 32px;
|
||||||
|
width: ${TABLE_CELL_GRIP_WIDTH};
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 32px;
|
|
||||||
width: 16px;
|
|
||||||
${({ isPendingRow }) =>
|
${({ isPendingRow }) =>
|
||||||
!isPendingRow
|
!isPendingRow
|
||||||
? css`
|
? css`
|
||||||
|
|||||||
@ -50,8 +50,10 @@ export const RecordTableColumnFooterAggregateValue = ({
|
|||||||
dropdownId,
|
dropdownId,
|
||||||
aggregateValue,
|
aggregateValue,
|
||||||
aggregateLabel,
|
aggregateLabel,
|
||||||
|
isFirstCell,
|
||||||
}: {
|
}: {
|
||||||
dropdownId: string;
|
dropdownId: string;
|
||||||
|
isFirstCell: boolean;
|
||||||
aggregateValue?: string | number | null;
|
aggregateValue?: string | number | null;
|
||||||
aggregateLabel?: string;
|
aggregateLabel?: string;
|
||||||
}) => {
|
}) => {
|
||||||
@ -66,13 +68,13 @@ export const RecordTableColumnFooterAggregateValue = ({
|
|||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
<StyledCell>
|
<StyledCell>
|
||||||
{isHovered || isDefined(aggregateValue) ? (
|
{isHovered || isDefined(aggregateValue) || isFirstCell ? (
|
||||||
<>
|
<>
|
||||||
<StyledText id={sanitizedId}>
|
<StyledText id={sanitizedId}>
|
||||||
{aggregateValue ?? 'Calculate'}
|
{aggregateValue ?? 'Calculate'}
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<StyledIcon fontWeight={'light'} size={theme.icon.size.sm} />
|
<StyledIcon fontWeight={'light'} size={theme.icon.size.sm} />
|
||||||
{aggregateValue && isDefined(aggregateLabel) && (
|
{isDefined(aggregateValue) && isDefined(aggregateLabel) && (
|
||||||
<AppTooltip
|
<AppTooltip
|
||||||
anchorSelect={`#${sanitizedId}`}
|
anchorSelect={`#${sanitizedId}`}
|
||||||
content={aggregateLabel}
|
content={aggregateLabel}
|
||||||
|
|||||||
@ -16,10 +16,12 @@ import { MenuItem } from 'twenty-ui';
|
|||||||
|
|
||||||
export const RecordTableColumnFooterDropdown = ({
|
export const RecordTableColumnFooterDropdown = ({
|
||||||
column,
|
column,
|
||||||
|
dropdownId,
|
||||||
}: {
|
}: {
|
||||||
column: ColumnDefinition<FieldMetadata>;
|
column: ColumnDefinition<FieldMetadata>;
|
||||||
|
dropdownId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { closeDropdown } = useDropdown(column.fieldMetadataId + '-footer');
|
const { closeDropdown } = useDropdown(dropdownId);
|
||||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ export const RecordTableColumnFooterDropdown = ({
|
|||||||
key={aggregation}
|
key={aggregation}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleAggregationChange(aggregation);
|
handleAggregationChange(aggregation);
|
||||||
|
closeDropdown();
|
||||||
}}
|
}}
|
||||||
text={getAggregateOperationLabel(aggregation)}
|
text={getAggregateOperationLabel(aggregation)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,53 +2,44 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
|||||||
import { RecordTableColumnFooterAggregateValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue';
|
import { RecordTableColumnFooterAggregateValue } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterAggregateValue';
|
||||||
import { RecordTableColumnFooterDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown';
|
import { RecordTableColumnFooterDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnFooterDropdown';
|
||||||
import { useAggregateRecordsForRecordTableColumnFooter } from '@/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter';
|
import { useAggregateRecordsForRecordTableColumnFooter } from '@/object-record/record-table/record-table-footer/hooks/useAggregateRecordsForRecordTableColumnFooter';
|
||||||
import { isScrollEnabledForRecordTableState } from '@/object-record/record-table/states/isScrollEnabledForRecordTableState';
|
|
||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
type RecordTableColumnFooterWithDropdownProps = {
|
type RecordTableColumnFooterWithDropdownProps = {
|
||||||
column: ColumnDefinition<FieldMetadata>;
|
column: ColumnDefinition<FieldMetadata>;
|
||||||
|
isFirstCell: boolean;
|
||||||
|
currentRecordGroupId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledDropdown = styled(Dropdown)`
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
|
||||||
|
|
||||||
transition: opacity 150ms ease-in-out;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RecordTableColumnFooterWithDropdown = ({
|
export const RecordTableColumnFooterWithDropdown = ({
|
||||||
column,
|
column,
|
||||||
|
currentRecordGroupId,
|
||||||
|
isFirstCell,
|
||||||
}: RecordTableColumnFooterWithDropdownProps) => {
|
}: RecordTableColumnFooterWithDropdownProps) => {
|
||||||
const setIsScrollEnabledForRecordTable = useSetRecoilComponentStateV2(
|
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||||
isScrollEnabledForRecordTableState,
|
useToggleScrollWrapper();
|
||||||
);
|
|
||||||
|
|
||||||
const handleDropdownOpen = useCallback(() => {
|
const handleDropdownOpen = useCallback(() => {
|
||||||
setIsScrollEnabledForRecordTable({
|
toggleScrollXWrapper(false);
|
||||||
enableXScroll: false,
|
toggleScrollYWrapper(false);
|
||||||
enableYScroll: false,
|
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||||
});
|
|
||||||
}, [setIsScrollEnabledForRecordTable]);
|
|
||||||
|
|
||||||
const handleDropdownClose = useCallback(() => {
|
const handleDropdownClose = useCallback(() => {
|
||||||
setIsScrollEnabledForRecordTable({
|
toggleScrollXWrapper(true);
|
||||||
enableXScroll: true,
|
toggleScrollYWrapper(true);
|
||||||
enableYScroll: true,
|
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||||
});
|
|
||||||
}, [setIsScrollEnabledForRecordTable]);
|
|
||||||
|
|
||||||
const { aggregateValue, aggregateLabel } =
|
const { aggregateValue, aggregateLabel } =
|
||||||
useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId);
|
useAggregateRecordsForRecordTableColumnFooter(column.fieldMetadataId);
|
||||||
|
|
||||||
const dropdownId = column.fieldMetadataId + '-footer';
|
const dropdownId = currentRecordGroupId
|
||||||
|
? `${column.fieldMetadataId}-footer-${currentRecordGroupId}`
|
||||||
|
: `${column.fieldMetadataId}-footer`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDropdown
|
<Dropdown
|
||||||
onOpen={handleDropdownOpen}
|
onOpen={handleDropdownOpen}
|
||||||
onClose={handleDropdownClose}
|
onClose={handleDropdownClose}
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
@ -57,9 +48,15 @@ export const RecordTableColumnFooterWithDropdown = ({
|
|||||||
aggregateLabel={aggregateLabel}
|
aggregateLabel={aggregateLabel}
|
||||||
aggregateValue={aggregateValue}
|
aggregateValue={aggregateValue}
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
|
isFirstCell={isFirstCell}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
dropdownComponents={
|
||||||
|
<RecordTableColumnFooterDropdown
|
||||||
|
column={column}
|
||||||
|
dropdownId={dropdownId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
dropdownComponents={<RecordTableColumnFooterDropdown column={column} />}
|
|
||||||
dropdownOffset={{ x: -1 }}
|
dropdownOffset={{ x: -1 }}
|
||||||
dropdownPlacement="bottom-start"
|
dropdownPlacement="bottom-start"
|
||||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { TABLE_CELL_CHECKBOX_MIN_WIDTH } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||||
|
import { TABLE_CELL_GRIP_WIDTH } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
|
||||||
import { RecordTableFooterCell } from '@/object-record/record-table/record-table-footer/components/RecordTableFooterCell';
|
import { RecordTableFooterCell } from '@/object-record/record-table/record-table-footer/components/RecordTableFooterCell';
|
||||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
@ -75,21 +77,33 @@ const StyledTableFoot = styled.thead`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledDiv = styled.div`
|
const StyledDiv = styled.div`
|
||||||
width: 30px;
|
width: calc(${TABLE_CELL_GRIP_WIDTH} + ${TABLE_CELL_CHECKBOX_MIN_WIDTH});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordTableFooter = () => {
|
export const RecordTableFooter = ({
|
||||||
|
currentRecordGroupId,
|
||||||
|
}: {
|
||||||
|
currentRecordGroupId?: string;
|
||||||
|
}) => {
|
||||||
const visibleTableColumns = useRecoilComponentValueV2(
|
const visibleTableColumns = useRecoilComponentValueV2(
|
||||||
visibleTableColumnsComponentSelector,
|
visibleTableColumnsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableFoot id="record-table-footer" data-select-disable>
|
<StyledTableFoot
|
||||||
|
id={`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
|
||||||
|
data-select-disable
|
||||||
|
>
|
||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th />
|
||||||
<StyledDiv />
|
<StyledDiv />
|
||||||
{visibleTableColumns.map((column) => (
|
{visibleTableColumns.map((column, index) => (
|
||||||
<RecordTableFooterCell key={column.fieldMetadataId} column={column} />
|
<RecordTableFooterCell
|
||||||
|
key={`${column.fieldMetadataId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
|
||||||
|
column={column}
|
||||||
|
currentRecordGroupId={currentRecordGroupId}
|
||||||
|
isFirstCell={index === 0}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
</StyledTableFoot>
|
</StyledTableFoot>
|
||||||
|
|||||||
@ -63,8 +63,12 @@ const StyledColumnFootContainer = styled.div`
|
|||||||
|
|
||||||
export const RecordTableFooterCell = ({
|
export const RecordTableFooterCell = ({
|
||||||
column,
|
column,
|
||||||
|
isFirstCell = false,
|
||||||
|
currentRecordGroupId,
|
||||||
}: {
|
}: {
|
||||||
column: ColumnDefinition<FieldMetadata>;
|
column: ColumnDefinition<FieldMetadata>;
|
||||||
|
isFirstCell?: boolean;
|
||||||
|
currentRecordGroupId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const tableColumns = useRecoilComponentValueV2(tableColumnsComponentState);
|
const tableColumns = useRecoilComponentValueV2(tableColumnsComponentState);
|
||||||
const tableColumnsByKey = useMemo(
|
const tableColumnsByKey = useMemo(
|
||||||
@ -82,7 +86,11 @@ export const RecordTableFooterCell = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<StyledColumnFootContainer>
|
<StyledColumnFootContainer>
|
||||||
<RecordTableColumnFooterWithDropdown column={column} />
|
<RecordTableColumnFooterWithDropdown
|
||||||
|
column={column}
|
||||||
|
currentRecordGroupId={currentRecordGroupId}
|
||||||
|
isFirstCell={isFirstCell}
|
||||||
|
/>
|
||||||
</StyledColumnFootContainer>
|
</StyledColumnFootContainer>
|
||||||
</StyledColumnFooterCell>
|
</StyledColumnFooterCell>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||||
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||||
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeViewRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
||||||
|
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||||
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
|
||||||
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
import { recordIndexViewFilterGroupsState } from '@/object-record/record-index/states/recordIndexViewFilterGroupsState';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
@ -16,8 +17,9 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
|||||||
const isAggregateQueryEnabled = useIsFeatureEnabled(
|
const isAggregateQueryEnabled = useIsFeatureEnabled(
|
||||||
'IS_AGGREGATE_QUERY_ENABLED',
|
'IS_AGGREGATE_QUERY_ENABLED',
|
||||||
);
|
);
|
||||||
|
|
||||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
|
const { recordGroupFilter } = useRecordGroupFilter(objectMetadataItem.fields);
|
||||||
|
|
||||||
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
const { currentViewWithSavedFiltersAndSorts } = useGetCurrentView();
|
||||||
const recordIndexViewFilterGroups = useRecoilValue(
|
const recordIndexViewFilterGroups = useRecoilValue(
|
||||||
recordIndexViewFilterGroupsState,
|
recordIndexViewFilterGroupsState,
|
||||||
@ -56,7 +58,7 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
|
|||||||
const { data } = useAggregateRecords({
|
const { data } = useAggregateRecords({
|
||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
recordGqlFieldsAggregate,
|
recordGqlFieldsAggregate,
|
||||||
filter: { ...requestFilters },
|
filter: { ...requestFilters, ...recordGroupFilter },
|
||||||
skip:
|
skip:
|
||||||
!isAggregateQueryEnabled || !isDefined(aggregateOperationForViewField),
|
!isAggregateQueryEnabled || !isDefined(aggregateOperationForViewField),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { isScrollEnabledForRecordTableState } from '@/object-record/record-table/states/isScrollEnabledForRecordTableState';
|
|
||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { RecordTableColumnHead } from './RecordTableColumnHead';
|
import { RecordTableColumnHead } from './RecordTableColumnHead';
|
||||||
@ -21,23 +20,18 @@ const StyledDropdown = styled(Dropdown)`
|
|||||||
export const RecordTableColumnHeadWithDropdown = ({
|
export const RecordTableColumnHeadWithDropdown = ({
|
||||||
column,
|
column,
|
||||||
}: RecordTableColumnHeadWithDropdownProps) => {
|
}: RecordTableColumnHeadWithDropdownProps) => {
|
||||||
const setIsScrollEnabledForRecordTable = useSetRecoilComponentStateV2(
|
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||||
isScrollEnabledForRecordTableState,
|
useToggleScrollWrapper();
|
||||||
);
|
|
||||||
|
|
||||||
const handleDropdownOpen = useCallback(() => {
|
const handleDropdownOpen = useCallback(() => {
|
||||||
setIsScrollEnabledForRecordTable({
|
toggleScrollXWrapper(false);
|
||||||
enableXScroll: false,
|
toggleScrollYWrapper(false);
|
||||||
enableYScroll: false,
|
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||||
});
|
|
||||||
}, [setIsScrollEnabledForRecordTable]);
|
|
||||||
|
|
||||||
const handleDropdownClose = useCallback(() => {
|
const handleDropdownClose = useCallback(() => {
|
||||||
setIsScrollEnabledForRecordTable({
|
toggleScrollXWrapper(true);
|
||||||
enableXScroll: true,
|
toggleScrollYWrapper(true);
|
||||||
enableYScroll: true,
|
}, [toggleScrollXWrapper, toggleScrollYWrapper]);
|
||||||
});
|
|
||||||
}, [setIsScrollEnabledForRecordTable]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDropdown
|
<StyledDropdown
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
|
||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
|
||||||
|
|
||||||
export type ScrollEnabled = {
|
|
||||||
enableXScroll: boolean;
|
|
||||||
enableYScroll: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isScrollEnabledForRecordTableState =
|
|
||||||
createComponentStateV2<ScrollEnabled>({
|
|
||||||
key: 'isScrollEnabledForRecordTableState',
|
|
||||||
defaultValue: {
|
|
||||||
enableXScroll: true,
|
|
||||||
enableYScroll: true,
|
|
||||||
},
|
|
||||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
|
||||||
});
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
|
||||||
|
|
||||||
export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & {
|
export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & {
|
||||||
size: number;
|
size: number;
|
||||||
@ -10,5 +9,4 @@ export type ColumnDefinition<T extends FieldMetadata> = FieldDefinition<T> & {
|
|||||||
viewFieldId?: string;
|
viewFieldId?: string;
|
||||||
isFilterable?: boolean;
|
isFilterable?: boolean;
|
||||||
isSortable?: boolean;
|
isSortable?: boolean;
|
||||||
aggregateOperation?: AGGREGATE_OPERATIONS | null;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,7 +30,10 @@ export const SettingsPageContainer = ({
|
|||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) => (
|
}) => (
|
||||||
<ScrollWrapper contextProviderName="settingsPageContainer">
|
<ScrollWrapper
|
||||||
|
contextProviderName="settingsPageContainer"
|
||||||
|
componentInstanceId={'scroll-wrapper-settings-page-container'}
|
||||||
|
>
|
||||||
<StyledSettingsPageContainer>{children}</StyledSettingsPageContainer>
|
<StyledSettingsPageContainer>{children}</StyledSettingsPageContainer>
|
||||||
</ScrollWrapper>
|
</ScrollWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useId } from 'react';
|
||||||
|
|
||||||
const StyledDropdownMenuItemsExternalContainer = styled.div<{
|
const StyledDropdownMenuItemsExternalContainer = styled.div<{
|
||||||
hasMaxHeight?: boolean;
|
hasMaxHeight?: boolean;
|
||||||
@ -44,13 +45,18 @@ export const DropdownMenuItemsContainer = ({
|
|||||||
className?: string;
|
className?: string;
|
||||||
withoutScrollWrapper?: boolean;
|
withoutScrollWrapper?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const id = useId();
|
||||||
|
|
||||||
return withoutScrollWrapper === true ? (
|
return withoutScrollWrapper === true ? (
|
||||||
<StyledDropdownMenuItemsExternalContainer
|
<StyledDropdownMenuItemsExternalContainer
|
||||||
hasMaxHeight={hasMaxHeight}
|
hasMaxHeight={hasMaxHeight}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
{hasMaxHeight ? (
|
{hasMaxHeight ? (
|
||||||
<StyledScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
<StyledScrollWrapper
|
||||||
|
contextProviderName="dropdownMenuItemsContainer"
|
||||||
|
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
||||||
|
>
|
||||||
<StyledDropdownMenuItemsInternalContainer>
|
<StyledDropdownMenuItemsInternalContainer>
|
||||||
{children}
|
{children}
|
||||||
</StyledDropdownMenuItemsInternalContainer>
|
</StyledDropdownMenuItemsInternalContainer>
|
||||||
@ -62,7 +68,10 @@ export const DropdownMenuItemsContainer = ({
|
|||||||
)}
|
)}
|
||||||
</StyledDropdownMenuItemsExternalContainer>
|
</StyledDropdownMenuItemsExternalContainer>
|
||||||
) : (
|
) : (
|
||||||
<ScrollWrapper contextProviderName="dropdownMenuItemsContainer">
|
<ScrollWrapper
|
||||||
|
contextProviderName="dropdownMenuItemsContainer"
|
||||||
|
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
||||||
|
>
|
||||||
<StyledDropdownMenuItemsExternalContainer
|
<StyledDropdownMenuItemsExternalContainer
|
||||||
hasMaxHeight={hasMaxHeight}
|
hasMaxHeight={hasMaxHeight}
|
||||||
className={className}
|
className={className}
|
||||||
|
|||||||
@ -31,7 +31,10 @@ export const ShowPageContainer = ({ children }: ShowPageContainerProps) => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
return isMobile ? (
|
return isMobile ? (
|
||||||
<StyledOuterContainer>
|
<StyledOuterContainer>
|
||||||
<StyledScrollWrapper contextProviderName="showPageContainer">
|
<StyledScrollWrapper
|
||||||
|
contextProviderName="showPageContainer"
|
||||||
|
componentInstanceId={'scroll-wrapper-show-page-container'}
|
||||||
|
>
|
||||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||||
</StyledScrollWrapper>
|
</StyledScrollWrapper>
|
||||||
</StyledOuterContainer>
|
</StyledOuterContainer>
|
||||||
|
|||||||
@ -23,7 +23,10 @@ export const ShowPageActivityContainer = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return !isNewViewableRecordLoading ? (
|
return !isNewViewableRecordLoading ? (
|
||||||
<ScrollWrapper contextProviderName="showPageActivityContainer">
|
<ScrollWrapper
|
||||||
|
contextProviderName="showPageActivityContainer"
|
||||||
|
componentInstanceId={`scroll-wrapper-tab-list-${targetableObject.id}`}
|
||||||
|
>
|
||||||
<StyledShowPageActivityContainer>
|
<StyledShowPageActivityContainer>
|
||||||
<RichTextEditor
|
<RichTextEditor
|
||||||
activityId={targetableObject.id}
|
activityId={targetableObject.id}
|
||||||
|
|||||||
@ -46,7 +46,10 @@ export const ShowPageLeftContainer = ({
|
|||||||
{children}
|
{children}
|
||||||
</StyledInnerContainer>
|
</StyledInnerContainer>
|
||||||
) : (
|
) : (
|
||||||
<ScrollWrapper contextProviderName="showPageLeftContainer">
|
<ScrollWrapper
|
||||||
|
contextProviderName="showPageLeftContainer"
|
||||||
|
componentInstanceId={`scroll-wrapper-show-page-left-container`}
|
||||||
|
>
|
||||||
<StyledIntermediateContainer>
|
<StyledIntermediateContainer>
|
||||||
<StyledInnerContainer isMobile={isMobile}>
|
<StyledInnerContainer isMobile={isMobile}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -7,8 +7,9 @@ import { TabListScope } from '@/ui/layout/tab/scopes/TabListScope';
|
|||||||
|
|
||||||
import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab/components/TabListFromUrlOptionalEffect';
|
import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab/components/TabListFromUrlOptionalEffect';
|
||||||
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
|
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
|
||||||
import { Tab } from './Tab';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { Tab } from './Tab';
|
||||||
|
|
||||||
export type SingleTabProps = {
|
export type SingleTabProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -70,26 +71,32 @@ export const TabList = ({
|
|||||||
componentInstanceId={tabListInstanceId}
|
componentInstanceId={tabListInstanceId}
|
||||||
tabListIds={tabs.map((tab) => tab.id)}
|
tabListIds={tabs.map((tab) => tab.id)}
|
||||||
/>
|
/>
|
||||||
<StyledTabsContainer>
|
<ScrollWrapper
|
||||||
{visibleTabs.map((tab) => (
|
defaultEnableYScroll={false}
|
||||||
<Tab
|
contextProviderName="tabList"
|
||||||
id={tab.id}
|
componentInstanceId={`scroll-wrapper-tab-list-${tabListInstanceId}`}
|
||||||
key={tab.id}
|
>
|
||||||
title={tab.title}
|
<StyledTabsContainer>
|
||||||
Icon={tab.Icon}
|
{visibleTabs.map((tab) => (
|
||||||
logo={tab.logo}
|
<Tab
|
||||||
active={tab.id === activeTabId}
|
id={tab.id}
|
||||||
disabled={tab.disabled ?? loading}
|
key={tab.id}
|
||||||
pill={tab.pill}
|
title={tab.title}
|
||||||
to={behaveAsLinks ? `#${tab.id}` : undefined}
|
Icon={tab.Icon}
|
||||||
onClick={() => {
|
logo={tab.logo}
|
||||||
if (!behaveAsLinks) {
|
active={tab.id === activeTabId}
|
||||||
setActiveTabId(tab.id);
|
disabled={tab.disabled ?? loading}
|
||||||
}
|
pill={tab.pill}
|
||||||
}}
|
to={behaveAsLinks ? `#${tab.id}` : undefined}
|
||||||
/>
|
onClick={() => {
|
||||||
))}
|
if (!behaveAsLinks) {
|
||||||
</StyledTabsContainer>
|
setActiveTabId(tab.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledTabsContainer>
|
||||||
|
</ScrollWrapper>
|
||||||
</TabListScope>
|
</TabListScope>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,15 +2,17 @@ import styled from '@emotion/styled';
|
|||||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ContextProviderName,
|
ContextProviderName,
|
||||||
getContextByProviderName,
|
getContextByProviderName,
|
||||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||||
import { useScrollStates } from '@/ui/utilities/scroll/hooks/internal/useScrollStates';
|
|
||||||
import { overlayScrollbarsState } from '@/ui/utilities/scroll/states/overlayScrollbarsState';
|
|
||||||
|
|
||||||
|
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||||
|
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||||
|
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||||
|
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
|
||||||
const StyledScrollWrapper = styled.div<{ scrollHide?: boolean }>`
|
const StyledScrollWrapper = styled.div<{ scrollHide?: boolean }>`
|
||||||
@ -31,41 +33,52 @@ const StyledInnerContainer = styled.div`
|
|||||||
export type ScrollWrapperProps = {
|
export type ScrollWrapperProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
enableXScroll?: boolean;
|
defaultEnableXScroll?: boolean;
|
||||||
enableYScroll?: boolean;
|
defaultEnableYScroll?: boolean;
|
||||||
contextProviderName: ContextProviderName;
|
contextProviderName: ContextProviderName;
|
||||||
scrollHide?: boolean;
|
scrollHide?: boolean;
|
||||||
|
componentInstanceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ScrollWrapper = ({
|
export const ScrollWrapper = ({
|
||||||
|
componentInstanceId,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
enableXScroll = true,
|
defaultEnableXScroll = true,
|
||||||
enableYScroll = true,
|
defaultEnableYScroll = true,
|
||||||
contextProviderName,
|
contextProviderName,
|
||||||
scrollHide = false,
|
scrollHide = false,
|
||||||
}: ScrollWrapperProps) => {
|
}: ScrollWrapperProps) => {
|
||||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||||
const Context = getContextByProviderName(contextProviderName);
|
const Context = getContextByProviderName(contextProviderName);
|
||||||
|
|
||||||
const { scrollTopComponentState, scrollLeftComponentState } =
|
const setScrollTop = useSetRecoilComponentStateV2(
|
||||||
useScrollStates(contextProviderName);
|
scrollWrapperScrollTopComponentState,
|
||||||
const setScrollTop = useSetRecoilState(scrollTopComponentState);
|
componentInstanceId,
|
||||||
const setScrollLeft = useSetRecoilState(scrollLeftComponentState);
|
);
|
||||||
|
|
||||||
|
const setScrollLeft = useSetRecoilComponentStateV2(
|
||||||
|
scrollWrapperScrollLeftComponentState,
|
||||||
|
componentInstanceId,
|
||||||
|
);
|
||||||
|
|
||||||
const handleScroll = (overlayScroll: OverlayScrollbars) => {
|
const handleScroll = (overlayScroll: OverlayScrollbars) => {
|
||||||
const target = overlayScroll.elements().scrollOffsetElement;
|
const target = overlayScroll.elements().scrollOffsetElement;
|
||||||
setScrollTop(target.scrollTop);
|
setScrollTop(target.scrollTop);
|
||||||
setScrollLeft(target.scrollLeft);
|
setScrollLeft(target.scrollLeft);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setOverlayScrollbars = useSetRecoilState(overlayScrollbarsState);
|
const setOverlayScrollbars = useSetRecoilComponentStateV2(
|
||||||
|
scrollWrapperInstanceComponentState,
|
||||||
|
componentInstanceId,
|
||||||
|
);
|
||||||
|
|
||||||
const [initialize, instance] = useOverlayScrollbars({
|
const [initialize, instance] = useOverlayScrollbars({
|
||||||
options: {
|
options: {
|
||||||
scrollbars: { autoHide: 'scroll' },
|
scrollbars: { autoHide: 'scroll' },
|
||||||
overflow: {
|
overflow: {
|
||||||
x: enableXScroll ? undefined : 'hidden',
|
x: defaultEnableXScroll ? undefined : 'hidden',
|
||||||
y: enableYScroll ? undefined : 'hidden',
|
y: defaultEnableYScroll ? undefined : 'hidden',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
@ -84,19 +97,23 @@ export const ScrollWrapper = ({
|
|||||||
}, [instance, setOverlayScrollbars]);
|
}, [instance, setOverlayScrollbars]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Context.Provider
|
<ScrollWrapperComponentInstanceContext.Provider
|
||||||
value={{
|
value={{ instanceId: componentInstanceId }}
|
||||||
ref: scrollableRef,
|
|
||||||
id: contextProviderName,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<StyledScrollWrapper
|
<Context.Provider
|
||||||
ref={scrollableRef}
|
value={{
|
||||||
className={className}
|
ref: scrollableRef,
|
||||||
scrollHide={scrollHide}
|
id: contextProviderName,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
<StyledScrollWrapper
|
||||||
</StyledScrollWrapper>
|
ref={scrollableRef}
|
||||||
</Context.Provider>
|
className={className}
|
||||||
|
scrollHide={scrollHide}
|
||||||
|
>
|
||||||
|
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||||
|
</StyledScrollWrapper>
|
||||||
|
</Context.Provider>
|
||||||
|
</ScrollWrapperComponentInstanceContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
import {
|
|
||||||
ContextProviderName,
|
|
||||||
getContextByProviderName,
|
|
||||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
|
||||||
import { scrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollLeftComponentState';
|
|
||||||
import { scrollTopComponentState } from '@/ui/utilities/scroll/states/scrollTopComponentState';
|
|
||||||
|
|
||||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
|
||||||
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
export const useScrollStates = (contextProviderName: ContextProviderName) => {
|
|
||||||
const Context = getContextByProviderName(contextProviderName);
|
|
||||||
const context = useContext(Context);
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
throw new Error('Context not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id: scopeId } = context;
|
|
||||||
|
|
||||||
return {
|
|
||||||
scrollLeftComponentState: extractComponentState(
|
|
||||||
scrollLeftComponentState,
|
|
||||||
scopeId,
|
|
||||||
),
|
|
||||||
scrollTopComponentState: extractComponentState(
|
|
||||||
scrollTopComponentState,
|
|
||||||
scopeId,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import { ContextProviderName } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
|
||||||
import { useScrollStates } from '@/ui/utilities/scroll/hooks/internal/useScrollStates';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
export const useScrollLeftValue = (
|
|
||||||
contextProviderName: ContextProviderName,
|
|
||||||
) => {
|
|
||||||
const { scrollLeftComponentState } = useScrollStates(contextProviderName);
|
|
||||||
return useRecoilValue(scrollLeftComponentState);
|
|
||||||
};
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { ContextProviderName } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
|
||||||
import { useScrollStates } from '@/ui/utilities/scroll/hooks/internal/useScrollStates';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
|
|
||||||
export const useScrollTopValue = (contextProviderName: ContextProviderName) => {
|
|
||||||
const { scrollTopComponentState } = useScrollStates(contextProviderName);
|
|
||||||
return useRecoilValue(scrollTopComponentState);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
|
export const useToggleScrollWrapper = () => {
|
||||||
|
const instanceOverlay = useRecoilComponentValueV2(
|
||||||
|
scrollWrapperInstanceComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleScrollXWrapper = (isEnabled: boolean) => {
|
||||||
|
if (!instanceOverlay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceOverlay.options({
|
||||||
|
overflow: {
|
||||||
|
x: isEnabled ? 'scroll' : 'hidden',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleScrollYWrapper = (isEnabled: boolean) => {
|
||||||
|
if (!instanceOverlay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceOverlay.options({
|
||||||
|
overflow: {
|
||||||
|
y: isEnabled ? 'scroll' : 'hidden',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { toggleScrollXWrapper, toggleScrollYWrapper };
|
||||||
|
};
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||||
|
|
||||||
|
export const ScrollWrapperComponentInstanceContext =
|
||||||
|
createComponentInstanceContext();
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
|
||||||
import { createState } from 'twenty-ui';
|
|
||||||
|
|
||||||
export const overlayScrollbarsState = createState<OverlayScrollbars | null>({
|
|
||||||
key: 'scroll/overlayScrollbarsState',
|
|
||||||
defaultValue: null,
|
|
||||||
});
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
|
||||||
|
|
||||||
export const scrollLeftComponentState = createComponentState<number>({
|
|
||||||
key: 'scroll/scrollLeftComponentState',
|
|
||||||
defaultValue: 0,
|
|
||||||
});
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
|
||||||
|
|
||||||
export const scrollPositionState = createFamilyState({
|
|
||||||
key: 'scroll/scrollPositionState',
|
|
||||||
defaultValue: 0,
|
|
||||||
});
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
|
||||||
|
|
||||||
export const scrollTopComponentState = createComponentState<number>({
|
|
||||||
key: 'scroll/scrollTopComponentState',
|
|
||||||
defaultValue: 0,
|
|
||||||
});
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||||
|
|
||||||
|
export const scrollWrapperInstanceComponentState =
|
||||||
|
createComponentStateV2<OverlayScrollbars | null>({
|
||||||
|
key: 'scrollWrapperInstanceComponentState',
|
||||||
|
defaultValue: null,
|
||||||
|
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const scrollWrapperScrollLeftComponentState =
|
||||||
|
createComponentStateV2<number>({
|
||||||
|
key: 'scrollWrapperScrollLeftComponentState',
|
||||||
|
defaultValue: 0,
|
||||||
|
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const scrollWrapperScrollTopComponentState =
|
||||||
|
createComponentStateV2<number>({
|
||||||
|
key: 'scrollWrapperScrollTopComponentState',
|
||||||
|
defaultValue: 0,
|
||||||
|
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -14,6 +14,5 @@ export const mapColumnDefinitionsToViewFields = (
|
|||||||
size: columnDefinition.size,
|
size: columnDefinition.size,
|
||||||
isVisible: columnDefinition.isVisible ?? true,
|
isVisible: columnDefinition.isVisible ?? true,
|
||||||
definition: columnDefinition,
|
definition: columnDefinition,
|
||||||
aggregateOperation: columnDefinition.aggregateOperation,
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export const mapViewFieldsToColumnDefinitions = ({
|
|||||||
isLabelIdentifier,
|
isLabelIdentifier,
|
||||||
isVisible: isLabelIdentifier || viewField.isVisible,
|
isVisible: isLabelIdentifier || viewField.isVisible,
|
||||||
viewFieldId: viewField.id,
|
viewFieldId: viewField.id,
|
||||||
aggregateOperation: viewField.aggregateOperation,
|
|
||||||
isSortable: correspondingColumnDefinition.isSortable,
|
isSortable: correspondingColumnDefinition.isSortable,
|
||||||
isFilterable: correspondingColumnDefinition.isFilterable,
|
isFilterable: correspondingColumnDefinition.isFilterable,
|
||||||
defaultValue: correspondingColumnDefinition.defaultValue,
|
defaultValue: correspondingColumnDefinition.defaultValue,
|
||||||
|
|||||||
@ -118,7 +118,10 @@ export const Releases = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
<ScrollWrapper contextProviderName="releases">
|
<ScrollWrapper
|
||||||
|
contextProviderName="releases"
|
||||||
|
componentInstanceId="scroll-wrapper-releases"
|
||||||
|
>
|
||||||
<StyledReleaseContainer>
|
<StyledReleaseContainer>
|
||||||
{releases.map((release) => (
|
{releases.map((release) => (
|
||||||
<React.Fragment key={release.slug}>
|
<React.Fragment key={release.slug}>
|
||||||
|
|||||||
Reference in New Issue
Block a user