Issue Reference: Fixes #7105 Description: This pull request introduces adjustments to the styling of the RecordBoardColumnHeader component. The modifications enhance the layout and visual consistency of the Kanban board headers. Changes Made: Margin Adjustment: Increased the bottom margin from theme.spacing(2) to theme.spacing(6) for better spacing below the header. Header Container Enhancements: Added a background color sourced from the theme (theme.background.primary) to the header container for improved visibility and aesthetics. Set a fixed height of 40px for the header to ensure a consistent size across different screens. Applied a fixed position to the header container to keep it visible at the top during scrolling. Added padding at the top using theme.spacing(2) for better alignment of content within the header. Before : https://github.com/user-attachments/assets/fd1c2d65-5e50-489a-a388-c0c4e1bd015b Now : [now.webm](https://github.com/user-attachments/assets/bd4cfb86-fc14-4902-b84c-99d27b07859e) --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -4,6 +4,8 @@ import { useContext, useRef } from 'react';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
||||
import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect';
|
||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
|
||||
@ -19,31 +21,26 @@ import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/get
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
||||
|
||||
export type RecordBoardProps = {
|
||||
recordBoardId: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
min-height: calc(100% - 1px);
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
const StyledColumnContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledContainerContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBoardHeader = styled.div`
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
const StyledBoardContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const RecordBoardScrollRestoreEffect = () => {
|
||||
@ -51,8 +48,8 @@ const RecordBoardScrollRestoreEffect = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
||||
const { updateOneRecord, selectFieldMetadataItem } =
|
||||
export const RecordBoard = () => {
|
||||
const { updateOneRecord, selectFieldMetadataItem, recordBoardId } =
|
||||
useContext(RecordBoardContext);
|
||||
const boardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -75,7 +72,7 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
||||
|
||||
useScopedHotkeys([Key.Escape], resetRecordSelection, TableHotkeyScope.Table);
|
||||
|
||||
const onDragEnd: OnDragEndResponder = useRecoilCallback(
|
||||
const handleDragEnd: OnDragEndResponder = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(result) => {
|
||||
if (!result.destination) return;
|
||||
@ -146,27 +143,32 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
|
||||
onColumnsChange={() => {}}
|
||||
onFieldsChange={() => {}}
|
||||
>
|
||||
<StyledWrapper>
|
||||
<StyledBoardHeader />
|
||||
<ScrollWrapper contextProviderName="recordBoard">
|
||||
<StyledContainer ref={boardRef}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
{columnIds.map((columnId) => (
|
||||
<RecordBoardColumn
|
||||
key={columnId}
|
||||
recordBoardColumnId={columnId}
|
||||
/>
|
||||
))}
|
||||
</DragDropContext>
|
||||
</StyledContainer>
|
||||
<RecordBoardScrollRestoreEffect />
|
||||
</ScrollWrapper>
|
||||
<DragSelect
|
||||
dragSelectable={boardRef}
|
||||
onDragSelectionStart={resetRecordSelection}
|
||||
onDragSelectionChange={setRecordAsSelected}
|
||||
/>
|
||||
</StyledWrapper>
|
||||
<ScrollWrapper contextProviderName="recordBoard">
|
||||
<RecordBoardStickyHeaderEffect />
|
||||
<StyledContainerContainer>
|
||||
<RecordBoardHeader />
|
||||
<StyledBoardContentContainer>
|
||||
<StyledContainer ref={boardRef}>
|
||||
<DragDropContext onDragEnd={handleDragEnd}>
|
||||
<StyledColumnContainer>
|
||||
{columnIds.map((columnId) => (
|
||||
<RecordBoardColumn
|
||||
key={columnId}
|
||||
recordBoardColumnId={columnId}
|
||||
/>
|
||||
))}
|
||||
</StyledColumnContainer>
|
||||
</DragDropContext>
|
||||
</StyledContainer>
|
||||
<RecordBoardScrollRestoreEffect />
|
||||
<DragSelect
|
||||
dragSelectable={boardRef}
|
||||
onDragSelectionStart={resetRecordSelection}
|
||||
onDragSelectionChange={setRecordAsSelected}
|
||||
/>
|
||||
</StyledBoardContentContainer>
|
||||
</StyledContainerContainer>
|
||||
</ScrollWrapper>
|
||||
</RecordBoardScope>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||
import { RecordBoardColumnHeaderWrapper } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledHeaderContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 40px;
|
||||
z-index: 10;
|
||||
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
|
||||
&.header-sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RecordBoardHeader = () => {
|
||||
const { columnIdsState } = useRecordBoardStates();
|
||||
|
||||
const columnIds = useRecoilValue(columnIdsState);
|
||||
|
||||
return (
|
||||
<StyledHeaderContainer id="record-board-header">
|
||||
{columnIds.map((columnId) => (
|
||||
<RecordBoardColumnHeaderWrapper columnId={columnId} />
|
||||
))}
|
||||
</StyledHeaderContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useScrollTopValue } from '@/ui/utilities/scroll/hooks/useScrollTopValue';
|
||||
|
||||
export const RecordBoardStickyHeaderEffect = () => {
|
||||
const scrollTop = useScrollTopValue('recordBoard');
|
||||
|
||||
// TODO: move this outside because it might cause way too many re-renders for other hooks
|
||||
useEffect(() => {
|
||||
if (scrollTop > 0) {
|
||||
document
|
||||
.getElementById('record-board-header')
|
||||
?.classList.add('header-sticky');
|
||||
} else {
|
||||
document
|
||||
.getElementById('record-board-header')
|
||||
?.classList.remove('header-sticky');
|
||||
}
|
||||
}, [scrollTop]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -16,6 +16,7 @@ type RecordBoardContextProps = {
|
||||
updateOneRecordInput: Partial<Omit<ObjectRecord, 'id'>>;
|
||||
}) => void;
|
||||
deleteOneRecord: (idToDelete: string) => Promise<unknown>;
|
||||
recordBoardId: string;
|
||||
};
|
||||
|
||||
export const RecordBoardContext = createContext<RecordBoardContextProps>(
|
||||
|
||||
@ -4,7 +4,6 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||
import { RecordBoardColumnCardsContainer } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsContainer';
|
||||
import { RecordBoardColumnHeader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeader';
|
||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||
|
||||
const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
||||
@ -18,7 +17,12 @@ const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
||||
min-width: 200px;
|
||||
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
padding-top: 0px;
|
||||
|
||||
position: relative;
|
||||
|
||||
min-height: 100%;
|
||||
`;
|
||||
|
||||
type RecordBoardColumnProps = {
|
||||
@ -61,12 +65,13 @@ export const RecordBoardColumn = ({
|
||||
isFirstColumn: isFirstColumn,
|
||||
isLastColumn: isLastColumn,
|
||||
recordCount: recordIds.length,
|
||||
columnId: recordBoardColumnId,
|
||||
recordIds,
|
||||
}}
|
||||
>
|
||||
<Droppable droppableId={recordBoardColumnId}>
|
||||
{(droppableProvided) => (
|
||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||
<RecordBoardColumnHeader />
|
||||
<RecordBoardColumnCardsContainer
|
||||
droppableProvided={droppableProvided}
|
||||
recordIds={recordIds}
|
||||
|
||||
@ -21,7 +21,6 @@ const StyledHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: left;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -45,6 +44,7 @@ const StyledHeaderActions = styled.div`
|
||||
margin-left: auto;
|
||||
`;
|
||||
const StyledHeaderContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
@ -59,13 +59,29 @@ const StyledRightContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-left: 1px solid
|
||||
${({ theme, isFirstColumn }) =>
|
||||
isFirstColumn ? 'none' : theme.border.color.light};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 200px;
|
||||
min-width: 200px;
|
||||
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const RecordBoardColumnHeader = () => {
|
||||
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
||||
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
||||
const { objectMetadataItem } = useContext(RecordBoardContext);
|
||||
const { columnDefinition, recordCount } = useContext(
|
||||
const { columnDefinition, isFirstColumn, recordCount } = useContext(
|
||||
RecordBoardColumnContext,
|
||||
);
|
||||
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
|
||||
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
|
||||
|
||||
const { objectMetadataItem } = useContext(RecordBoardContext);
|
||||
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
@ -94,7 +110,8 @@ export const RecordBoardColumnHeader = () => {
|
||||
handleNewButtonClick,
|
||||
handleCreateSuccess,
|
||||
handleEntitySelect,
|
||||
} = useColumnNewCardActions(columnDefinition.id);
|
||||
} = useColumnNewCardActions(columnDefinition?.id ?? '');
|
||||
|
||||
const { isOpportunitiesCompanyFieldDisabled } =
|
||||
useIsOpportunitiesCompanyFieldDisabled();
|
||||
|
||||
@ -103,7 +120,7 @@ export const RecordBoardColumnHeader = () => {
|
||||
!isOpportunitiesCompanyFieldDisabled;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledColumn isFirstColumn={isFirstColumn}>
|
||||
<StyledHeader
|
||||
onMouseEnter={() => setIsHeaderHovered(true)}
|
||||
onMouseLeave={() => setIsHeaderHovered(false)}
|
||||
@ -181,6 +198,6 @@ export const RecordBoardColumnHeader = () => {
|
||||
position="first"
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
</StyledColumn>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||
import { RecordBoardColumnHeader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeader';
|
||||
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
type RecordBoardColumnHeaderWrapperProps = {
|
||||
columnId: string;
|
||||
};
|
||||
|
||||
export const RecordBoardColumnHeaderWrapper = ({
|
||||
columnId,
|
||||
}: RecordBoardColumnHeaderWrapperProps) => {
|
||||
const {
|
||||
isFirstColumnFamilyState,
|
||||
isLastColumnFamilyState,
|
||||
columnsFamilySelector,
|
||||
recordIdsByColumnIdFamilyState,
|
||||
} = useRecordBoardStates();
|
||||
|
||||
const columnDefinition = useRecoilValue(columnsFamilySelector(columnId));
|
||||
|
||||
const isFirstColumn = useRecoilValue(isFirstColumnFamilyState(columnId));
|
||||
|
||||
const isLastColumn = useRecoilValue(isLastColumnFamilyState(columnId));
|
||||
|
||||
const recordIds = useRecoilValue(recordIdsByColumnIdFamilyState(columnId));
|
||||
|
||||
if (!isDefined(columnDefinition)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordBoardColumnContext.Provider
|
||||
value={{
|
||||
columnId,
|
||||
columnDefinition: columnDefinition,
|
||||
isFirstColumn: isFirstColumn,
|
||||
isLastColumn: isLastColumn,
|
||||
recordCount: recordIds.length,
|
||||
recordIds,
|
||||
}}
|
||||
>
|
||||
<RecordBoardColumnHeader />
|
||||
</RecordBoardColumnContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -7,6 +7,8 @@ type RecordBoardColumnContextProps = {
|
||||
isFirstColumn: boolean;
|
||||
isLastColumn: boolean;
|
||||
recordCount: number;
|
||||
columnId: string;
|
||||
recordIds: string[];
|
||||
};
|
||||
|
||||
export const RecordBoardColumnContext =
|
||||
|
||||
@ -12,6 +12,7 @@ type RecordBoardScopeProps = {
|
||||
onColumnsChange: (column: RecordBoardColumnDefinition[]) => void;
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
export const RecordBoardScope = ({
|
||||
children,
|
||||
recordBoardScopeId,
|
||||
|
||||
@ -46,9 +46,10 @@ export const RecordIndexBoardContainer = ({
|
||||
createOneRecord,
|
||||
updateOneRecord,
|
||||
deleteOneRecord,
|
||||
recordBoardId,
|
||||
}}
|
||||
>
|
||||
<RecordBoard recordBoardId={recordBoardId} />
|
||||
<RecordBoard />
|
||||
</RecordBoardContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,8 +2,6 @@ import styled from '@emotion/styled';
|
||||
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { RecordIndexBoardContainer } from '@/object-record/record-index/components/RecordIndexBoardContainer';
|
||||
import { RecordIndexBoardDataLoader } from '@/object-record/record-index/components/RecordIndexBoardDataLoader';
|
||||
import { RecordIndexBoardDataLoaderEffect } from '@/object-record/record-index/components/RecordIndexBoardDataLoaderEffect';
|
||||
@ -45,12 +43,13 @@ const StyledContainer = styled.div`
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledContainerWithPadding = styled.div<{ fullHeight?: boolean }>`
|
||||
height: ${({ fullHeight }) => (fullHeight ? '100%' : 'auto')};
|
||||
padding-left: ${({ theme }) => theme.table.horizontalCellPadding};
|
||||
const StyledContainerWithPadding = styled.div`
|
||||
height: calc(100% - 40px);
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RecordIndexContainer = () => {
|
||||
@ -58,17 +57,12 @@ export const RecordIndexContainer = () => {
|
||||
recordIndexViewTypeState,
|
||||
);
|
||||
|
||||
const { objectNamePlural, recordIndexId } = useContext(
|
||||
RecordIndexRootPropsContext,
|
||||
);
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
const {
|
||||
objectNamePlural,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
recordIndexId,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
});
|
||||
} = useContext(RecordIndexRootPropsContext);
|
||||
|
||||
const { columnDefinitions, filterDefinitions, sortDefinitions } =
|
||||
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
@ -120,69 +114,56 @@ export const RecordIndexContainer = () => {
|
||||
>
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<SpreadsheetImportProvider>
|
||||
<StyledContainerWithPadding>
|
||||
<ViewBar
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<RecordIndexOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewType={recordIndexViewType ?? ViewType.Table}
|
||||
/>
|
||||
<ViewBar
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<RecordIndexOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewType={recordIndexViewType ?? ViewType.Table}
|
||||
/>
|
||||
}
|
||||
onCurrentViewChange={(view) => {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
onCurrentViewChange={(view) => {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
onViewFieldsChange(view.viewFields);
|
||||
setTableFilters(
|
||||
mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterDefinitions,
|
||||
),
|
||||
);
|
||||
setRecordIndexFilters(
|
||||
mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterDefinitions,
|
||||
),
|
||||
);
|
||||
setTableSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexViewType(view.type);
|
||||
setRecordIndexViewKanbanFieldMetadataIdState(
|
||||
view.kanbanFieldMetadataId,
|
||||
);
|
||||
setRecordIndexIsCompactModeActive(view.isCompact);
|
||||
}}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
onViewFieldsChange(view.viewFields);
|
||||
setTableFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setRecordIndexFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setTableSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexViewType(view.type);
|
||||
setRecordIndexViewKanbanFieldMetadataIdState(
|
||||
view.kanbanFieldMetadataId,
|
||||
);
|
||||
setRecordIndexIsCompactModeActive(view.isCompact);
|
||||
}}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</SpreadsheetImportProvider>
|
||||
|
||||
{recordIndexViewType === ViewType.Table && (
|
||||
<>
|
||||
<RecordIndexTableContainer
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexTableContainerEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexTableContainerEffect />
|
||||
</>
|
||||
)}
|
||||
{recordIndexViewType === ViewType.Kanban && (
|
||||
<StyledContainerWithPadding fullHeight>
|
||||
<StyledContainerWithPadding>
|
||||
<RecordIndexBoardContainer
|
||||
recordBoardId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
|
||||
@ -1,26 +1,23 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { contextStoreCurrentObjectMetadataIdState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdState';
|
||||
import { contextStoreTargetedRecordIdsState } from '@/context-store/states/contextStoreTargetedRecordIdsState';
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
|
||||
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
|
||||
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
|
||||
|
||||
type RecordIndexTableContainerEffectProps = {
|
||||
objectNameSingular: string;
|
||||
recordTableId: string;
|
||||
viewBarId: string;
|
||||
};
|
||||
export const RecordIndexTableContainerEffect = () => {
|
||||
const { recordIndexId, objectNameSingular } = useContext(
|
||||
RecordIndexRootPropsContext,
|
||||
);
|
||||
|
||||
const viewBarId = recordIndexId;
|
||||
|
||||
export const RecordIndexTableContainerEffect = ({
|
||||
objectNameSingular,
|
||||
recordTableId,
|
||||
viewBarId,
|
||||
}: RecordIndexTableContainerEffectProps) => {
|
||||
const {
|
||||
setAvailableTableColumns,
|
||||
setOnEntityCountChange,
|
||||
@ -28,7 +25,7 @@ export const RecordIndexTableContainerEffect = ({
|
||||
setOnToggleColumnFilter,
|
||||
setOnToggleColumnSort,
|
||||
} = useRecordTable({
|
||||
recordTableId,
|
||||
recordTableId: recordIndexId,
|
||||
});
|
||||
|
||||
const setContextStoreTargetedRecordIds = useSetRecoilState(
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { createRootPropsContext } from '~/utils/createRootPropsContext';
|
||||
|
||||
export type RecordIndexRootPropsContextProps = {
|
||||
@ -6,6 +7,7 @@ export type RecordIndexRootPropsContextProps = {
|
||||
onCreateRecord: () => void;
|
||||
objectNamePlural: string;
|
||||
objectNameSingular: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
recordIndexId: string;
|
||||
};
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ import { RecordTableContext } from '@/object-record/record-table/contexts/Record
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
|
||||
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 { useScrollLeftValue } from '@/ui/utilities/scroll/hooks/useScrollLeftValue';
|
||||
import { useScrollTopValue } from '@/ui/utilities/scroll/hooks/useScrollTopValue';
|
||||
@ -41,16 +40,12 @@ export const RecordTableBodyEffect = () => {
|
||||
const tableLastRowVisible = useRecoilValue(tableLastRowVisibleState);
|
||||
|
||||
const scrollTop = useScrollTopValue('recordTableWithWrappers');
|
||||
const setIsRecordTableScrolledTop = useSetRecoilComponentState(
|
||||
isRecordTableScrolledTopComponentState,
|
||||
);
|
||||
|
||||
const setHasRecordTableFetchedAllRecordsComponents =
|
||||
useSetRecoilComponentState(hasRecordTableFetchedAllRecordsComponentStateV2);
|
||||
|
||||
// TODO: move this outside because it might cause way too many re-renders for other hooks
|
||||
useEffect(() => {
|
||||
setIsRecordTableScrolledTop(scrollTop === 0);
|
||||
if (scrollTop > 0) {
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
@ -60,7 +55,7 @@ export const RecordTableBodyEffect = () => {
|
||||
.getElementById('record-table-header')
|
||||
?.classList.remove('header-sticky');
|
||||
}
|
||||
}, [scrollTop, setIsRecordTableScrolledTop]);
|
||||
}, [scrollTop]);
|
||||
|
||||
const scrollLeft = useScrollLeftValue('recordTableWithWrappers');
|
||||
|
||||
|
||||
@ -8,10 +8,7 @@ import { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/re
|
||||
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;
|
||||
}>`
|
||||
const StyledTableHead = styled.thead`
|
||||
cursor: pointer;
|
||||
|
||||
th:nth-of-type(1) {
|
||||
|
||||
@ -26,7 +26,6 @@ const StyledColumnHeaderCell = styled.th<{
|
||||
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;
|
||||
|
||||
@ -17,7 +17,6 @@ const StyledColumnHeaderCell = styled.th`
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
border-right: transparent;
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
max-width: 30px;
|
||||
min-width: 30px;
|
||||
width: 30px;
|
||||
|
||||
@ -22,7 +22,6 @@ const StyledPlusIconHeaderCell = styled.th<{
|
||||
`;
|
||||
}};
|
||||
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};
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
||||
import { createComponentStateV2_alpha } from '@/ui/utilities/state/component-state/utils/createComponentStateV2_alpha';
|
||||
|
||||
export const isRecordTableScrolledTopComponentState =
|
||||
createComponentStateV2_alpha<boolean>({
|
||||
key: 'isRecordTableScrolledTopComponentState',
|
||||
componentContext: RecordTableScopeInternalContext,
|
||||
defaultValue: true,
|
||||
});
|
||||
@ -53,7 +53,7 @@ export const TabList = ({
|
||||
|
||||
return (
|
||||
<TabListScope tabListScopeId={tabListId}>
|
||||
<ScrollWrapper hideY contextProviderName="tabList">
|
||||
<ScrollWrapper enableYScroll={false} contextProviderName="tabList">
|
||||
<StyledContainer className={className}>
|
||||
{tabs
|
||||
.filter((tab) => !tab.hide)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
type TopBarProps = {
|
||||
className?: string;
|
||||
@ -10,14 +10,15 @@ type TopBarProps = {
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
border-bottom: ${({ theme }) => `1px solid ${theme.border.color.light}`};
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledTopBar = styled.div<{ displayBottomBorder: boolean }>`
|
||||
const StyledTopBar = styled.div`
|
||||
align-items: center;
|
||||
border-bottom: ${({ displayBottomBorder, theme }) =>
|
||||
displayBottomBorder ? `1px solid ${theme.border.color.light}` : 'none'};
|
||||
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
display: flex;
|
||||
@ -26,6 +27,8 @@ const StyledTopBar = styled.div<{ displayBottomBorder: boolean }>`
|
||||
height: 39px;
|
||||
justify-content: space-between;
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
z-index: 7;
|
||||
`;
|
||||
|
||||
@ -44,10 +47,9 @@ export const TopBar = ({
|
||||
leftComponent,
|
||||
rightComponent,
|
||||
bottomComponent,
|
||||
displayBottomBorder = true,
|
||||
}: TopBarProps) => (
|
||||
<StyledContainer className={className}>
|
||||
<StyledTopBar displayBottomBorder={displayBottomBorder}>
|
||||
<StyledTopBar>
|
||||
<StyledLeftSection>{leftComponent}</StyledLeftSection>
|
||||
<StyledRightSection>{rightComponent}</StyledRightSection>
|
||||
</StyledTopBar>
|
||||
|
||||
@ -26,16 +26,16 @@ const StyledScrollWrapper = styled.div`
|
||||
export type ScrollWrapperProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
hideY?: boolean;
|
||||
hideX?: boolean;
|
||||
enableXScroll?: boolean;
|
||||
enableYScroll?: boolean;
|
||||
contextProviderName: ContextProviderName;
|
||||
};
|
||||
|
||||
export const ScrollWrapper = ({
|
||||
children,
|
||||
className,
|
||||
hideX,
|
||||
hideY,
|
||||
enableXScroll = true,
|
||||
enableYScroll = true,
|
||||
contextProviderName,
|
||||
}: ScrollWrapperProps) => {
|
||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||
@ -58,8 +58,8 @@ export const ScrollWrapper = ({
|
||||
options: {
|
||||
scrollbars: { autoHide: 'scroll' },
|
||||
overflow: {
|
||||
y: hideY ? 'hidden' : undefined,
|
||||
x: hideX ? 'hidden' : undefined,
|
||||
x: enableXScroll ? undefined : 'hidden',
|
||||
y: enableYScroll ? undefined : 'hidden',
|
||||
},
|
||||
},
|
||||
events: {
|
||||
|
||||
@ -60,7 +60,6 @@ export const ViewBar = ({
|
||||
leftComponent={
|
||||
loading ? <ViewBarSkeletonLoader /> : <ViewPickerDropdown />
|
||||
}
|
||||
displayBottomBorder={false}
|
||||
rightComponent={
|
||||
<>
|
||||
<ObjectFilterDropdownButton
|
||||
|
||||
@ -61,6 +61,7 @@ export const RecordIndexPage = () => {
|
||||
recordIndexId,
|
||||
objectNamePlural,
|
||||
objectNameSingular,
|
||||
objectMetadataItem,
|
||||
onIndexRecordsLoaded: handleIndexRecordsLoaded,
|
||||
onIndexIdentifierClick: handleIndexIdentifierClick,
|
||||
onCreateRecord: handleCreateRecord,
|
||||
|
||||
Reference in New Issue
Block a user