5421 box shadow on frozen header and first column (#6130)
- Refactored components in table - Added a isTableRecordScrolledLeftState and isTableRecordScrolledTopState to subscribe to table scroll - Added a zIndex logic that subscribes to those new states in new tinier components --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,87 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { DragDropContext, Droppable, 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 { 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 DraggableTableBody = ({
|
||||
objectNameSingular,
|
||||
draggableItems,
|
||||
recordTableId,
|
||||
}: DraggableTableBodyProps) => {
|
||||
const [v4Persistable] = useState(v4());
|
||||
|
||||
const { tableRowIdsState } = useRecordTableStates();
|
||||
|
||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
||||
|
||||
const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } =
|
||||
useGetCurrentView(recordTableId);
|
||||
|
||||
const viewSorts = currentViewWithCombinedFiltersAndSorts?.viewSorts || [];
|
||||
|
||||
const setIsRemoveSortingModalOpenState = useSetRecoilState(
|
||||
isRemoveSortingModalOpenState,
|
||||
);
|
||||
const computeNewRowPosition = useComputeNewRowPosition();
|
||||
|
||||
const handleDragEnd = (result: DropResult) => {
|
||||
if (viewSorts.length > 0) {
|
||||
setIsRemoveSortingModalOpenState(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const computeResult = computeNewRowPosition(result, tableRowIds);
|
||||
|
||||
if (!isDefined(computeResult)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateOneRow({
|
||||
idToUpdate: computeResult.draggedRecordId,
|
||||
updateOneRecordInput: {
|
||||
position: computeResult.newPosition,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={handleDragEnd}>
|
||||
<Droppable droppableId={v4Persistable}>
|
||||
{(provided) => (
|
||||
<StyledTbody
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
<RecordTablePendingRow />
|
||||
{draggableItems}
|
||||
{provided.placeholder}
|
||||
</StyledTbody>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
@ -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 && (
|
||||
<DropdownMenu
|
||||
disableBlur={disableBlur}
|
||||
width={dropdownMenuWidth ?? dropdownWidth}
|
||||
data-select-disable
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
>
|
||||
{dropdownComponents}
|
||||
</DropdownMenu>
|
||||
<FloatingPortal>
|
||||
<DropdownMenu
|
||||
disableBlur={disableBlur}
|
||||
width={dropdownMenuWidth ?? dropdownWidth}
|
||||
data-select-disable
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
>
|
||||
{dropdownComponents}
|
||||
</DropdownMenu>
|
||||
</FloatingPortal>
|
||||
)}
|
||||
<DropdownOnToggleEffect
|
||||
onDropdownClose={onClose}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Chip, ChipVariant } from 'twenty-ui';
|
||||
|
||||
import { AnimatedContainer } from '@/object-record/record-table/components/AnimatedContainer';
|
||||
import { ExpandedListDropdown } from '@/ui/layout/expandable-list/components/ExpandedListDropdown';
|
||||
import { isFirstOverflowingChildElement } from '@/ui/layout/expandable-list/utils/isFirstOverflowingChildElement';
|
||||
import { AnimatedContainer } from '@/ui/utilities/animation/components/AnimatedContainer';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import React from 'react';
|
||||
|
||||
export const AnimatedContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.1 }}
|
||||
whileHover={{ scale: 1.04 }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
@ -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 = <StateType>(
|
||||
componentState: ComponentState<StateType>,
|
||||
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 }),
|
||||
);
|
||||
};
|
||||
@ -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 = <StateType>(
|
||||
componentState: ComponentState<StateType>,
|
||||
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 }),
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { RecoilState } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
|
||||
export type ComponentState<StateType> = {
|
||||
key: string;
|
||||
atomFamily: (componentStateKey: ComponentStateKey) => RecoilState<StateType>;
|
||||
};
|
||||
@ -2,15 +2,17 @@ import { AtomEffect, atomFamily } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
|
||||
type CreateComponentStateType<ValueType> = {
|
||||
key: string;
|
||||
defaultValue: ValueType;
|
||||
effects?: AtomEffect<ValueType>[];
|
||||
};
|
||||
|
||||
export const createComponentState = <ValueType>({
|
||||
key,
|
||||
defaultValue,
|
||||
effects,
|
||||
}: {
|
||||
key: string;
|
||||
defaultValue: ValueType;
|
||||
effects?: AtomEffect<ValueType>[];
|
||||
}) => {
|
||||
}: CreateComponentStateType<ValueType>) => {
|
||||
return atomFamily<ValueType, ComponentStateKey>({
|
||||
key,
|
||||
default: defaultValue,
|
||||
|
||||
@ -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<ValueType> = {
|
||||
key: string;
|
||||
defaultValue: ValueType;
|
||||
componentContext?: ScopeInternalContext<any> | null;
|
||||
effects?: AtomEffect<ValueType>[];
|
||||
};
|
||||
|
||||
export const createComponentStateV2 = <ValueType>({
|
||||
key,
|
||||
defaultValue,
|
||||
componentContext,
|
||||
effects,
|
||||
}: CreateComponentStateV2Type<ValueType>): ComponentState<ValueType> => {
|
||||
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<ValueType, ComponentStateKey>({
|
||||
key,
|
||||
default: defaultValue,
|
||||
effects: effects,
|
||||
}),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user