2394-feat(front): create new record on click of plus icon (#2660)
* 2394-feat(front): create new record on click of plus icon * 2394-feat(front): fix of Icon Button * 2394-fix: PR fixes --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -6,7 +6,7 @@ import { AttachmentDropdown } from '@/activities/files/components/AttachmentDrop
|
|||||||
import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon';
|
import { AttachmentIcon } from '@/activities/files/components/AttachmentIcon';
|
||||||
import { Attachment } from '@/activities/files/types/Attachment';
|
import { Attachment } from '@/activities/files/types/Attachment';
|
||||||
import { downloadFile } from '@/activities/files/utils/downloadFile';
|
import { downloadFile } from '@/activities/files/utils/downloadFile';
|
||||||
import { useDeleteOneObjectRecord } from '@/object-record/hooks/useDeleteOneObjectRecord';
|
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||||
import { IconCalendar } from '@/ui/display/icon';
|
import { IconCalendar } from '@/ui/display/icon';
|
||||||
import {
|
import {
|
||||||
FieldContext,
|
FieldContext,
|
||||||
@ -60,8 +60,8 @@ export const AttachmentRow = ({ attachment }: { attachment: Attachment }) => {
|
|||||||
[attachment?.id],
|
[attachment?.id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { deleteOneObject: deleteOneAttachment } =
|
const { deleteOneRecord: deleteOneAttachment } =
|
||||||
useDeleteOneObjectRecord<Attachment>({
|
useDeleteOneRecord<Attachment>({
|
||||||
objectNameSingular: 'attachment',
|
objectNameSingular: 'attachment',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { Attachment } from '@/activities/files/types/Attachment';
|
|||||||
import { getFileType } from '@/activities/files/utils/getFileType';
|
import { getFileType } from '@/activities/files/utils/getFileType';
|
||||||
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
import { IconPlus } from '@/ui/display/icon';
|
import { IconPlus } from '@/ui/display/icon';
|
||||||
import { Button } from '@/ui/input/button/components/Button';
|
import { Button } from '@/ui/input/button/components/Button';
|
||||||
import { FileFolder, useUploadFileMutation } from '~/generated/graphql';
|
import { FileFolder, useUploadFileMutation } from '~/generated/graphql';
|
||||||
@ -65,8 +65,8 @@ export const Attachments = ({
|
|||||||
|
|
||||||
const [uploadFile] = useUploadFileMutation();
|
const [uploadFile] = useUploadFileMutation();
|
||||||
|
|
||||||
const { createOneObject: createOneAttachment } =
|
const { createOneRecord: createOneAttachment } =
|
||||||
useCreateOneObjectRecord<Attachment>({
|
useCreateOneRecord<Attachment>({
|
||||||
objectNameSingular: 'attachment',
|
objectNameSingular: 'attachment',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Attachment } from '@/activities/files/types/Attachment';
|
import { Attachment } from '@/activities/files/types/Attachment';
|
||||||
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
|
|
||||||
import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
|
import { ActivityTargetableEntity } from '../../types/ActivityTargetableEntity';
|
||||||
|
|
||||||
export const useAttachments = (entity: ActivityTargetableEntity) => {
|
export const useAttachments = (entity: ActivityTargetableEntity) => {
|
||||||
const { objects: attachments } = useFindManyObjectRecords({
|
const { records: attachments } = useFindManyRecords({
|
||||||
objectNamePlural: 'attachments',
|
objectNamePlural: 'attachments',
|
||||||
filter: {
|
filter: {
|
||||||
[entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id },
|
[entity.type === 'Company' ? 'companyId' : 'personId']: { eq: entity.id },
|
||||||
|
|||||||
@ -24,8 +24,10 @@ const StyledContainer = styled.div`
|
|||||||
|
|
||||||
export const RecordTableContainer = ({
|
export const RecordTableContainer = ({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
|
createRecord,
|
||||||
}: {
|
}: {
|
||||||
objectNamePlural: string;
|
objectNamePlural: string;
|
||||||
|
createRecord: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
|
const { objectMetadataItem: foundObjectMetadataItem } = useObjectMetadataItem(
|
||||||
{
|
{
|
||||||
@ -87,7 +89,8 @@ export const RecordTableContainer = ({
|
|||||||
<RecordTable
|
<RecordTable
|
||||||
recordTableId={recordTableId}
|
recordTableId={recordTableId}
|
||||||
viewBarId={viewBarId}
|
viewBarId={viewBarId}
|
||||||
updateEntityMutation={updateEntity}
|
updateRecordMutation={updateEntity}
|
||||||
|
createRecord={createRecord}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -67,7 +67,10 @@ export const RecordTablePage = () => {
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<StyledTableContainer>
|
<StyledTableContainer>
|
||||||
<RecordTableContainer objectNamePlural={objectNamePlural} />
|
<RecordTableContainer
|
||||||
|
objectNamePlural={objectNamePlural}
|
||||||
|
createRecord={handleAddButtonClick}
|
||||||
|
/>
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
<RecordTableActionBar />
|
<RecordTableActionBar />
|
||||||
<RecordTableContextMenu />
|
<RecordTableContextMenu />
|
||||||
|
|||||||
@ -33,7 +33,8 @@ export const SignInBackgroundMockContainer = () => {
|
|||||||
<RecordTable
|
<RecordTable
|
||||||
recordTableId={recordTableId}
|
recordTableId={recordTableId}
|
||||||
viewBarId={viewBarId}
|
viewBarId={viewBarId}
|
||||||
updateEntityMutation={() => {}}
|
createRecord={() => {}}
|
||||||
|
updateRecordMutation={() => {}}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useRef } from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { RecordTableHeader } from '@/ui/object/record-table/components/RecordTableHeader';
|
||||||
import { RecordTableInternalEffect } from '@/ui/object/record-table/components/RecordTableInternalEffect';
|
import { RecordTableInternalEffect } from '@/ui/object/record-table/components/RecordTableInternalEffect';
|
||||||
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable';
|
||||||
import { RecordTableScope } from '@/ui/object/record-table/scopes/RecordTableScope';
|
import { RecordTableScope } from '@/ui/object/record-table/scopes/RecordTableScope';
|
||||||
@ -13,7 +14,6 @@ import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinit
|
|||||||
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||||
|
|
||||||
import { RecordTableBody } from './RecordTableBody';
|
import { RecordTableBody } from './RecordTableBody';
|
||||||
import { RecordTableHeader } from './RecordTableHeader';
|
|
||||||
|
|
||||||
const StyledTable = styled.table`
|
const StyledTable = styled.table`
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@ -77,13 +77,15 @@ const StyledTableContainer = styled.div`
|
|||||||
type RecordTableProps = {
|
type RecordTableProps = {
|
||||||
recordTableId: string;
|
recordTableId: string;
|
||||||
viewBarId: string;
|
viewBarId: string;
|
||||||
updateEntityMutation: (params: any) => void;
|
updateRecordMutation: (params: any) => void;
|
||||||
|
createRecord: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTable = ({
|
export const RecordTable = ({
|
||||||
|
updateRecordMutation,
|
||||||
|
createRecord,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
viewBarId,
|
viewBarId,
|
||||||
updateEntityMutation,
|
|
||||||
}: RecordTableProps) => {
|
}: RecordTableProps) => {
|
||||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -100,12 +102,12 @@ export const RecordTable = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ScrollWrapper>
|
<ScrollWrapper>
|
||||||
<EntityUpdateMutationContext.Provider value={updateEntityMutation}>
|
<EntityUpdateMutationContext.Provider value={updateRecordMutation}>
|
||||||
<StyledTableWithHeader>
|
<StyledTableWithHeader>
|
||||||
<StyledTableContainer>
|
<StyledTableContainer>
|
||||||
<div ref={tableBodyRef}>
|
<div ref={tableBodyRef}>
|
||||||
<StyledTable className="entity-table-cell">
|
<StyledTable className="entity-table-cell">
|
||||||
<RecordTableHeader />
|
<RecordTableHeader createRecord={createRecord} />
|
||||||
<RecordTableBody />
|
<RecordTableBody />
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
<DragSelect
|
<DragSelect
|
||||||
|
|||||||
@ -1,76 +1,21 @@
|
|||||||
import { useCallback, useState } from 'react';
|
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { IconPlus } from '@/ui/display/icon';
|
import { IconPlus } from '@/ui/display/icon';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
import { RecordTableHeaderCell } from '@/ui/object/record-table/components/RecordTableHeaderCell';
|
||||||
|
|
||||||
import { useRecordTableScopedStates } from '../hooks/internal/useRecordTableScopedStates';
|
import { useRecordTableScopedStates } from '../hooks/internal/useRecordTableScopedStates';
|
||||||
import { useTableColumns } from '../hooks/useTableColumns';
|
|
||||||
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
|
|
||||||
|
|
||||||
import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown';
|
|
||||||
import { RecordTableHeaderPlusButtonContent } from './RecordTableHeaderPlusButtonContent';
|
import { RecordTableHeaderPlusButtonContent } from './RecordTableHeaderPlusButtonContent';
|
||||||
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
||||||
|
|
||||||
const COLUMN_MIN_WIDTH = 104;
|
|
||||||
|
|
||||||
const StyledColumnHeaderCell = styled.th<{
|
|
||||||
columnWidth: number;
|
|
||||||
isResizing?: boolean;
|
|
||||||
}>`
|
|
||||||
${({ columnWidth }) => `
|
|
||||||
min-width: ${columnWidth}px;
|
|
||||||
width: ${columnWidth}px;
|
|
||||||
`}
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
${({ theme }) => {
|
|
||||||
return `
|
|
||||||
&:hover {
|
|
||||||
background: ${theme.background.transparent.light};
|
|
||||||
};
|
|
||||||
`;
|
|
||||||
}};
|
|
||||||
${({ isResizing, theme }) => {
|
|
||||||
if (isResizing) {
|
|
||||||
return `&:after {
|
|
||||||
background-color: ${theme.color.blue};
|
|
||||||
bottom: 0;
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
right: -1px;
|
|
||||||
top: 0;
|
|
||||||
width: 2px;
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledResizeHandler = styled.div`
|
|
||||||
bottom: 0;
|
|
||||||
cursor: col-resize;
|
|
||||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
|
||||||
position: absolute;
|
|
||||||
right: -9px;
|
|
||||||
top: 0;
|
|
||||||
width: 3px;
|
|
||||||
z-index: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTableHead = styled.thead`
|
const StyledTableHead = styled.thead`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledColumnHeadContainer = styled.div`
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledPlusIconHeaderCell = styled.th`
|
const StyledPlusIconHeaderCell = styled.th`
|
||||||
${({ theme }) => {
|
${({ theme }) => {
|
||||||
return `
|
return `
|
||||||
@ -101,83 +46,17 @@ const HIDDEN_TABLE_COLUMN_DROPDOWN_SCOPE_ID =
|
|||||||
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
|
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
|
||||||
'hidden-table-columns-dropdown-hotkey-scope-id';
|
'hidden-table-columns-dropdown-hotkey-scope-id';
|
||||||
|
|
||||||
export const RecordTableHeader = () => {
|
export const RecordTableHeader = ({
|
||||||
const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState(
|
createRecord,
|
||||||
resizeFieldOffsetState,
|
}: {
|
||||||
);
|
createRecord: () => void;
|
||||||
|
}) => {
|
||||||
|
const { hiddenTableColumnsSelector, visibleTableColumnsSelector } =
|
||||||
|
useRecordTableScopedStates();
|
||||||
|
|
||||||
const {
|
|
||||||
tableColumnsState,
|
|
||||||
tableColumnsByKeySelector,
|
|
||||||
hiddenTableColumnsSelector,
|
|
||||||
visibleTableColumnsSelector,
|
|
||||||
} = useRecordTableScopedStates();
|
|
||||||
|
|
||||||
const tableColumns = useRecoilValue(tableColumnsState);
|
|
||||||
const tableColumnsByKey = useRecoilValue(tableColumnsByKeySelector);
|
|
||||||
const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector);
|
const hiddenTableColumns = useRecoilValue(hiddenTableColumnsSelector);
|
||||||
const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector);
|
const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector);
|
||||||
|
|
||||||
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
|
|
||||||
number | null
|
|
||||||
>(null);
|
|
||||||
const [resizedFieldKey, setResizedFieldKey] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const { handleColumnsChange } = useTableColumns();
|
|
||||||
|
|
||||||
const handleResizeHandlerStart = useCallback((positionX: number) => {
|
|
||||||
setInitialPointerPositionX(positionX);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleResizeHandlerMove = useCallback(
|
|
||||||
(positionX: number) => {
|
|
||||||
if (!initialPointerPositionX) return;
|
|
||||||
setResizeFieldOffset(positionX - initialPointerPositionX);
|
|
||||||
},
|
|
||||||
[setResizeFieldOffset, initialPointerPositionX],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleResizeHandlerEnd = useRecoilCallback(
|
|
||||||
({ snapshot, set }) =>
|
|
||||||
async () => {
|
|
||||||
if (!resizedFieldKey) return;
|
|
||||||
|
|
||||||
const nextWidth = Math.round(
|
|
||||||
Math.max(
|
|
||||||
tableColumnsByKey[resizedFieldKey].size +
|
|
||||||
snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(),
|
|
||||||
COLUMN_MIN_WIDTH,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
set(resizeFieldOffsetState, 0);
|
|
||||||
setInitialPointerPositionX(null);
|
|
||||||
setResizedFieldKey(null);
|
|
||||||
|
|
||||||
if (nextWidth !== tableColumnsByKey[resizedFieldKey].size) {
|
|
||||||
const nextColumns = tableColumns.map((column) =>
|
|
||||||
column.fieldMetadataId === resizedFieldKey
|
|
||||||
? { ...column, size: nextWidth }
|
|
||||||
: column,
|
|
||||||
);
|
|
||||||
|
|
||||||
await handleColumnsChange(nextColumns);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[resizedFieldKey, tableColumnsByKey, tableColumns, handleColumnsChange],
|
|
||||||
);
|
|
||||||
|
|
||||||
useTrackPointer({
|
|
||||||
shouldTrackPointer: resizedFieldKey !== null,
|
|
||||||
onMouseDown: handleResizeHandlerStart,
|
|
||||||
onMouseMove: handleResizeHandlerMove,
|
|
||||||
onMouseUp: handleResizeHandlerEnd,
|
|
||||||
});
|
|
||||||
|
|
||||||
const primaryColumn = visibleTableColumns.find(
|
|
||||||
(column) => column.position === 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -193,35 +72,11 @@ export const RecordTableHeader = () => {
|
|||||||
<SelectAllCheckbox />
|
<SelectAllCheckbox />
|
||||||
</th>
|
</th>
|
||||||
{visibleTableColumns.map((column) => (
|
{visibleTableColumns.map((column) => (
|
||||||
<StyledColumnHeaderCell
|
<RecordTableHeaderCell
|
||||||
key={column.fieldMetadataId}
|
key={column.fieldMetadataId}
|
||||||
isResizing={resizedFieldKey === column.fieldMetadataId}
|
column={column}
|
||||||
columnWidth={Math.max(
|
createRecord={createRecord}
|
||||||
tableColumnsByKey[column.fieldMetadataId].size +
|
/>
|
||||||
(resizedFieldKey === column.fieldMetadataId
|
|
||||||
? resizeFieldOffset
|
|
||||||
: 0),
|
|
||||||
COLUMN_MIN_WIDTH,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<StyledColumnHeadContainer>
|
|
||||||
<ColumnHeadWithDropdown
|
|
||||||
column={column}
|
|
||||||
isFirstColumn={column.position === 1}
|
|
||||||
isLastColumn={
|
|
||||||
column.position === visibleTableColumns.length - 1
|
|
||||||
}
|
|
||||||
primaryColumnKey={primaryColumn?.fieldMetadataId || ''}
|
|
||||||
/>
|
|
||||||
</StyledColumnHeadContainer>
|
|
||||||
<StyledResizeHandler
|
|
||||||
className="cursor-col-resize"
|
|
||||||
role="separator"
|
|
||||||
onPointerDown={() => {
|
|
||||||
setResizedFieldKey(column.fieldMetadataId);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</StyledColumnHeaderCell>
|
|
||||||
))}
|
))}
|
||||||
{hiddenTableColumns.length > 0 && (
|
{hiddenTableColumns.length > 0 && (
|
||||||
<StyledPlusIconHeaderCell>
|
<StyledPlusIconHeaderCell>
|
||||||
|
|||||||
@ -0,0 +1,200 @@
|
|||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { IconPlus } from '@/ui/display/icon';
|
||||||
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
|
import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata';
|
||||||
|
import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates';
|
||||||
|
import { useTableColumns } from '@/ui/object/record-table/hooks/useTableColumns';
|
||||||
|
import { resizeFieldOffsetState } from '@/ui/object/record-table/states/resizeFieldOffsetState';
|
||||||
|
import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition';
|
||||||
|
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||||
|
|
||||||
|
import { ColumnHeadWithDropdown } from './ColumnHeadWithDropdown';
|
||||||
|
|
||||||
|
const COLUMN_MIN_WIDTH = 104;
|
||||||
|
|
||||||
|
const StyledColumnHeaderCell = styled.th<{
|
||||||
|
columnWidth: number;
|
||||||
|
isResizing?: boolean;
|
||||||
|
}>`
|
||||||
|
${({ columnWidth }) => `
|
||||||
|
min-width: ${columnWidth}px;
|
||||||
|
width: ${columnWidth}px;
|
||||||
|
`}
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
${({ theme }) => {
|
||||||
|
return `
|
||||||
|
&:hover {
|
||||||
|
background: ${theme.background.transparent.light};
|
||||||
|
};
|
||||||
|
`;
|
||||||
|
}};
|
||||||
|
${({ isResizing, theme }) => {
|
||||||
|
if (isResizing) {
|
||||||
|
return `&:after {
|
||||||
|
background-color: ${theme.color.blue};
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: -1px;
|
||||||
|
top: 0;
|
||||||
|
width: 2px;
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledResizeHandler = styled.div`
|
||||||
|
bottom: 0;
|
||||||
|
cursor: col-resize;
|
||||||
|
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||||
|
position: absolute;
|
||||||
|
right: -9px;
|
||||||
|
top: 0;
|
||||||
|
width: 3px;
|
||||||
|
z-index: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledColumnHeadContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledHeaderIcon = styled.div`
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||||
|
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||||
|
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RecordTableHeaderCell = ({
|
||||||
|
column,
|
||||||
|
createRecord,
|
||||||
|
}: {
|
||||||
|
column: ColumnDefinition<FieldMetadata>;
|
||||||
|
createRecord: () => void;
|
||||||
|
}) => {
|
||||||
|
const [resizeFieldOffset, setResizeFieldOffset] = useRecoilState(
|
||||||
|
resizeFieldOffsetState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
tableColumnsState,
|
||||||
|
tableColumnsByKeySelector,
|
||||||
|
visibleTableColumnsSelector,
|
||||||
|
} = useRecordTableScopedStates();
|
||||||
|
|
||||||
|
const tableColumns = useRecoilValue(tableColumnsState);
|
||||||
|
const tableColumnsByKey = useRecoilValue(tableColumnsByKeySelector);
|
||||||
|
const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector);
|
||||||
|
|
||||||
|
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
|
||||||
|
number | null
|
||||||
|
>(null);
|
||||||
|
const [resizedFieldKey, setResizedFieldKey] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { handleColumnsChange } = useTableColumns();
|
||||||
|
|
||||||
|
const handleResizeHandlerStart = useCallback((positionX: number) => {
|
||||||
|
setInitialPointerPositionX(positionX);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [iconVisibility, setIconVisibility] = useState(false);
|
||||||
|
|
||||||
|
const primaryColumn = visibleTableColumns.find(
|
||||||
|
(column) => column.position === 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleResizeHandlerMove = useCallback(
|
||||||
|
(positionX: number) => {
|
||||||
|
if (!initialPointerPositionX) return;
|
||||||
|
setResizeFieldOffset(positionX - initialPointerPositionX);
|
||||||
|
},
|
||||||
|
[setResizeFieldOffset, initialPointerPositionX],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleResizeHandlerEnd = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
async () => {
|
||||||
|
if (!resizedFieldKey) return;
|
||||||
|
|
||||||
|
const nextWidth = Math.round(
|
||||||
|
Math.max(
|
||||||
|
tableColumnsByKey[resizedFieldKey].size +
|
||||||
|
snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(),
|
||||||
|
COLUMN_MIN_WIDTH,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
set(resizeFieldOffsetState, 0);
|
||||||
|
setInitialPointerPositionX(null);
|
||||||
|
setResizedFieldKey(null);
|
||||||
|
|
||||||
|
if (nextWidth !== tableColumnsByKey[resizedFieldKey].size) {
|
||||||
|
const nextColumns = tableColumns.map((column) =>
|
||||||
|
column.fieldMetadataId === resizedFieldKey
|
||||||
|
? { ...column, size: nextWidth }
|
||||||
|
: column,
|
||||||
|
);
|
||||||
|
|
||||||
|
await handleColumnsChange(nextColumns);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[resizedFieldKey, tableColumnsByKey, tableColumns, handleColumnsChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
useTrackPointer({
|
||||||
|
shouldTrackPointer: resizedFieldKey !== null,
|
||||||
|
onMouseDown: handleResizeHandlerStart,
|
||||||
|
onMouseMove: handleResizeHandlerMove,
|
||||||
|
onMouseUp: handleResizeHandlerEnd,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledColumnHeaderCell
|
||||||
|
key={column.fieldMetadataId}
|
||||||
|
isResizing={resizedFieldKey === column.fieldMetadataId}
|
||||||
|
columnWidth={Math.max(
|
||||||
|
tableColumnsByKey[column.fieldMetadataId].size +
|
||||||
|
(resizedFieldKey === column.fieldMetadataId ? resizeFieldOffset : 0) +
|
||||||
|
24,
|
||||||
|
COLUMN_MIN_WIDTH,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<StyledColumnHeadContainer
|
||||||
|
onMouseEnter={() => setIconVisibility(true)}
|
||||||
|
onMouseLeave={() => setIconVisibility(false)}
|
||||||
|
>
|
||||||
|
<ColumnHeadWithDropdown
|
||||||
|
column={column}
|
||||||
|
isFirstColumn={column.position === 1}
|
||||||
|
isLastColumn={column.position === visibleTableColumns.length - 1}
|
||||||
|
primaryColumnKey={primaryColumn?.fieldMetadataId || ''}
|
||||||
|
/>
|
||||||
|
{iconVisibility && column.position === 0 && (
|
||||||
|
<StyledHeaderIcon>
|
||||||
|
<LightIconButton
|
||||||
|
Icon={IconPlus}
|
||||||
|
size="small"
|
||||||
|
accent="tertiary"
|
||||||
|
onClick={createRecord}
|
||||||
|
/>
|
||||||
|
</StyledHeaderIcon>
|
||||||
|
)}
|
||||||
|
</StyledColumnHeadContainer>
|
||||||
|
<StyledResizeHandler
|
||||||
|
className="cursor-col-resize"
|
||||||
|
role="separator"
|
||||||
|
onPointerDown={() => {
|
||||||
|
setResizedFieldKey(column.fieldMetadataId);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledColumnHeaderCell>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user