Improve mouse tracking (#1061)
* Improve mouse tracking * Fix lint * Fix regression on Filters * Fix according to review
This commit is contained in:
@ -14,9 +14,9 @@ import { PersonChip } from '@/people/components/PersonChip';
|
||||
import { useFilteredSearchPeopleQuery } from '@/people/queries';
|
||||
import { MultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
|
||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import { Activity, ActivityTarget, CommentableType } from '~/generated/graphql';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
|
||||
@ -3,7 +3,7 @@ import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||
import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState';
|
||||
import { viewFieldsFamilyState } from '@/ui/table/states/viewFieldsState';
|
||||
import { viewFieldsState } from '@/ui/table/states/viewFieldsState';
|
||||
|
||||
import { companyViewFields } from '../../constants/companyViewFields';
|
||||
|
||||
@ -13,7 +13,7 @@ export function CompanyTableMockData() {
|
||||
const setEntityTableDimensions = useSetRecoilState(
|
||||
entityTableDimensionsState,
|
||||
);
|
||||
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||
const setViewFields = useSetRecoilState(viewFieldsState);
|
||||
const setEntityTableData = useSetEntityTableData();
|
||||
|
||||
setEntityTableData(mockedCompaniesData, []);
|
||||
|
||||
@ -2,8 +2,8 @@ import { ReactElement, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { overlayBackground } from '@/ui/theme/constants/effects';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { BoardCardFieldHotkeyScope } from '../types/BoardCardFieldHotkeyScope';
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMen
|
||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
||||
import { icon } from '@/ui/theme/constants/icon';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
||||
|
||||
|
||||
@ -63,18 +63,20 @@ export function BoardHeader<SortField>({
|
||||
{viewName}
|
||||
</>
|
||||
}
|
||||
rightComponents={[
|
||||
<FilterDropdownButton
|
||||
context={context}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>,
|
||||
<SortDropdownButton<SortField>
|
||||
isSortSelected={sorts.length > 0}
|
||||
availableSorts={availableSorts || []}
|
||||
onSortSelect={sortSelect}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>,
|
||||
]}
|
||||
rightComponent={
|
||||
<>
|
||||
<FilterDropdownButton
|
||||
context={context}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>
|
||||
<SortDropdownButton<SortField>
|
||||
isSortSelected={sorts.length > 0}
|
||||
availableSorts={availableSorts || []}
|
||||
onSortSelect={sortSelect}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
bottomComponent={
|
||||
<SortAndFilterBar
|
||||
context={context}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMen
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import { IconPlus } from '@/ui/icon';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { useEntitySelectSearch } from '../hooks/useEntitySelectSearch';
|
||||
|
||||
@ -5,7 +5,7 @@ import { motion } from 'framer-motion';
|
||||
import {
|
||||
ClickOutsideMode,
|
||||
useListenClickOutside,
|
||||
} from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
|
||||
@ -5,11 +5,11 @@ import { motion } from 'framer-motion';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import {
|
||||
ClickOutsideMode,
|
||||
useListenClickOutside,
|
||||
} from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useUpdateViewFieldMutation } from '~/generated/graphql';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
||||
import { EntityUpdateMutationHookContext } from '../states/EntityUpdateMutationHookContext';
|
||||
import { viewFieldsFamilyState } from '../states/viewFieldsState';
|
||||
import { TableHeader } from '../table-header/components/TableHeader';
|
||||
|
||||
import { EntityTableBody } from './EntityTableBody';
|
||||
@ -102,11 +99,6 @@ export function EntityTable<SortField>({
|
||||
onSortsUpdate,
|
||||
useUpdateEntityMutation,
|
||||
}: OwnProps<SortField>) {
|
||||
const viewFields = useRecoilValue(viewFieldsFamilyState);
|
||||
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||
|
||||
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||
|
||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useMapKeyboardToSoftFocus();
|
||||
@ -120,25 +112,6 @@ export function EntityTable<SortField>({
|
||||
},
|
||||
});
|
||||
|
||||
const handleColumnResize = useCallback(
|
||||
(resizedFieldId: string, width: number) => {
|
||||
setViewFields((previousViewFields) =>
|
||||
previousViewFields.map((viewField) =>
|
||||
viewField.id === resizedFieldId
|
||||
? { ...viewField, columnSize: width }
|
||||
: viewField,
|
||||
),
|
||||
);
|
||||
updateViewFieldMutation({
|
||||
variables: {
|
||||
data: { sizeInPx: width },
|
||||
where: { id: resizedFieldId },
|
||||
},
|
||||
});
|
||||
},
|
||||
[setViewFields, updateViewFieldMutation],
|
||||
);
|
||||
|
||||
return (
|
||||
<EntityUpdateMutationHookContext.Provider value={useUpdateEntityMutation}>
|
||||
<StyledTableWithHeader>
|
||||
@ -150,15 +123,10 @@ export function EntityTable<SortField>({
|
||||
onSortsUpdate={onSortsUpdate}
|
||||
/>
|
||||
<StyledTableWrapper>
|
||||
{viewFields.length > 0 && (
|
||||
<StyledTable>
|
||||
<EntityTableHeader
|
||||
onColumnResize={handleColumnResize}
|
||||
viewFields={viewFields}
|
||||
/>
|
||||
<EntityTableBody />
|
||||
</StyledTable>
|
||||
)}
|
||||
<StyledTable>
|
||||
<EntityTableHeader />
|
||||
<EntityTableBody />
|
||||
</StyledTable>
|
||||
</StyledTableWrapper>
|
||||
</StyledTableContainer>
|
||||
</StyledTableWithHeader>
|
||||
|
||||
@ -34,14 +34,7 @@ export function EntityTableCell({ cellIndex }: { cellIndex: number }) {
|
||||
return (
|
||||
<RecoilScope>
|
||||
<ColumnIndexContext.Provider value={cellIndex}>
|
||||
<td
|
||||
onContextMenu={(event) => handleContextMenu(event)}
|
||||
style={{
|
||||
width: viewField.columnSize,
|
||||
minWidth: viewField.columnSize,
|
||||
maxWidth: viewField.columnSize,
|
||||
}}
|
||||
>
|
||||
<td onContextMenu={(event) => handleContextMenu(event)}>
|
||||
<GenericEditableCell viewField={viewField} />
|
||||
</td>
|
||||
</ColumnIndexContext.Provider>
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
import { PointerEvent, useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
useRecoilCallback,
|
||||
useRecoilState,
|
||||
useRecoilValue,
|
||||
useSetRecoilState,
|
||||
} from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../types/ViewField';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { useUpdateViewFieldMutation } from '~/generated/graphql';
|
||||
|
||||
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
|
||||
import { viewFieldsState } from '../states/viewFieldsState';
|
||||
|
||||
import { ColumnHead } from './ColumnHead';
|
||||
import { SelectAllCheckbox } from './SelectAllCheckbox';
|
||||
@ -42,12 +49,11 @@ const StyledResizeHandler = styled.div`
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type OwnProps = {
|
||||
onColumnResize: (resizedFieldId: string, width: number) => void;
|
||||
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
};
|
||||
export function EntityTableHeader() {
|
||||
const viewFields = useRecoilValue(viewFieldsState);
|
||||
const setViewFields = useSetRecoilState(viewFieldsState);
|
||||
|
||||
export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
|
||||
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||
const columnWidths = useMemo(
|
||||
() =>
|
||||
viewFields.reduce<Record<string, number>>(
|
||||
@ -59,46 +65,75 @@ export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
|
||||
),
|
||||
[viewFields],
|
||||
);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
|
||||
const [resizedFieldId, setResizedFieldId] = useState<string | null>(null);
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [offset, setOffset] = useRecoilState(resizeFieldOffsetState);
|
||||
|
||||
const handleResizeHandlerDragStart = useCallback(
|
||||
(event: PointerEvent<HTMLDivElement>, fieldId: string) => {
|
||||
setIsResizing(true);
|
||||
setResizedFieldId(fieldId);
|
||||
setInitialPointerPositionX(event.clientX);
|
||||
const handleColumnResize = useCallback(
|
||||
(resizedFieldId: string, width: number) => {
|
||||
setViewFields((previousViewFields) =>
|
||||
previousViewFields.map((viewField) =>
|
||||
viewField.id === resizedFieldId
|
||||
? { ...viewField, columnSize: width }
|
||||
: viewField,
|
||||
),
|
||||
);
|
||||
updateViewFieldMutation({
|
||||
variables: {
|
||||
data: { sizeInPx: width },
|
||||
where: { id: resizedFieldId },
|
||||
},
|
||||
});
|
||||
},
|
||||
[setIsResizing, setResizedFieldId, setInitialPointerPositionX],
|
||||
[setViewFields, updateViewFieldMutation],
|
||||
);
|
||||
|
||||
const handleResizeHandlerDrag = useCallback(
|
||||
(event: PointerEvent<HTMLDivElement>) => {
|
||||
if (!isResizing || initialPointerPositionX === null) return;
|
||||
|
||||
setOffset(event.clientX - initialPointerPositionX);
|
||||
const handleResizeHandlerStart = useCallback(
|
||||
(positionX: number, _: number) => {
|
||||
setInitialPointerPositionX(positionX);
|
||||
},
|
||||
[isResizing, initialPointerPositionX],
|
||||
[],
|
||||
);
|
||||
|
||||
const handleResizeHandlerDragEnd = useCallback(() => {
|
||||
setIsResizing(false);
|
||||
if (!resizedFieldId) return;
|
||||
const handleResizeHandlerMove = useCallback(
|
||||
(positionX: number, _positionY: number) => {
|
||||
if (!initialPointerPositionX) return;
|
||||
setOffset(positionX - initialPointerPositionX);
|
||||
},
|
||||
[setOffset, initialPointerPositionX],
|
||||
);
|
||||
|
||||
const nextWidth = Math.round(
|
||||
Math.max(columnWidths[resizedFieldId] + offset, COLUMN_MIN_WIDTH),
|
||||
);
|
||||
const handleResizeHandlerEnd = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(_positionX: number, _positionY: number) => {
|
||||
if (!resizedFieldId) return;
|
||||
const nextWidth = Math.round(
|
||||
Math.max(
|
||||
columnWidths[resizedFieldId] +
|
||||
snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(),
|
||||
COLUMN_MIN_WIDTH,
|
||||
),
|
||||
);
|
||||
|
||||
if (nextWidth !== columnWidths[resizedFieldId]) {
|
||||
onColumnResize(resizedFieldId, nextWidth);
|
||||
}
|
||||
if (nextWidth !== columnWidths[resizedFieldId]) {
|
||||
handleColumnResize(resizedFieldId, nextWidth);
|
||||
}
|
||||
set(resizeFieldOffsetState, 0);
|
||||
setInitialPointerPositionX(null);
|
||||
setResizedFieldId(null);
|
||||
},
|
||||
[resizedFieldId, columnWidths, setResizedFieldId, handleColumnResize],
|
||||
);
|
||||
|
||||
setOffset(0);
|
||||
}, [resizedFieldId, columnWidths, offset, onColumnResize]);
|
||||
useTrackPointer({
|
||||
shouldTrackPointer: resizedFieldId !== null,
|
||||
onMouseDown: handleResizeHandlerStart,
|
||||
onMouseMove: handleResizeHandlerMove,
|
||||
onMouseUp: handleResizeHandlerEnd,
|
||||
});
|
||||
|
||||
return (
|
||||
<thead>
|
||||
@ -116,7 +151,7 @@ export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
|
||||
{viewFields.map((viewField) => (
|
||||
<StyledColumnHeaderCell
|
||||
key={viewField.columnOrder.toString()}
|
||||
isResizing={isResizing && resizedFieldId === viewField.id}
|
||||
isResizing={resizedFieldId === viewField.id}
|
||||
style={{
|
||||
width: Math.max(
|
||||
columnWidths[viewField.id] +
|
||||
@ -132,12 +167,9 @@ export function EntityTableHeader({ onColumnResize, viewFields }: OwnProps) {
|
||||
<StyledResizeHandler
|
||||
className="cursor-col-resize"
|
||||
role="separator"
|
||||
onPointerDown={(event) =>
|
||||
handleResizeHandlerDragStart(event, viewField.id)
|
||||
}
|
||||
onPointerMove={handleResizeHandlerDrag}
|
||||
onPointerOut={handleResizeHandlerDragEnd}
|
||||
onPointerUp={handleResizeHandlerDragEnd}
|
||||
onPointerDown={() => {
|
||||
setResizedFieldId(viewField.id);
|
||||
}}
|
||||
/>
|
||||
</StyledColumnHeaderCell>
|
||||
))}
|
||||
|
||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ViewFieldContext } from '../states/ViewFieldContext';
|
||||
import { viewFieldsFamilyState } from '../states/viewFieldsState';
|
||||
import { viewFieldsState } from '../states/viewFieldsState';
|
||||
|
||||
import { CheckboxCell } from './CheckboxCell';
|
||||
import { EntityTableCell } from './EntityTableCell';
|
||||
@ -13,7 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>`
|
||||
`;
|
||||
|
||||
export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||
const viewFields = useRecoilValue(viewFieldsFamilyState);
|
||||
const viewFields = useRecoilValue(viewFieldsState);
|
||||
|
||||
return (
|
||||
<StyledRow data-testid={`row-id-${rowId}`} selected={false}>
|
||||
|
||||
@ -33,7 +33,6 @@ export function GenericEntityTableData({
|
||||
variables: { orderBy, where: whereFilters },
|
||||
onCompleted: (data: any) => {
|
||||
const entities = data[getRequestResultKey] ?? [];
|
||||
|
||||
setEntityTableData(entities, filterDefinitionArray);
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { useMoveSoftFocus } from '../../hooks/useMoveSoftFocus';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
@ -4,8 +4,8 @@ import { Key } from 'ts-key-enum';
|
||||
|
||||
import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit';
|
||||
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/click-outside/hooks/useListenClickOutside';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { useEditableCell } from '../../hooks/useEditableCell';
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
|
||||
import { viewFieldsFamilyState } from '../states/viewFieldsState';
|
||||
import { viewFieldsState } from '../states/viewFieldsState';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
@ -32,7 +32,7 @@ export const useLoadView = ({
|
||||
const setEntityTableDimensions = useSetRecoilState(
|
||||
entityTableDimensionsState,
|
||||
);
|
||||
const setViewFields = useSetRecoilState(viewFieldsFamilyState);
|
||||
const setViewFields = useSetRecoilState(viewFieldsState);
|
||||
|
||||
const [createViewFieldsMutation] = useCreateViewFieldsMutation();
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const resizeFieldOffsetState = atom<number>({
|
||||
key: 'resizeFieldOffsetState',
|
||||
default: 0,
|
||||
});
|
||||
@ -2,9 +2,7 @@ import { atom } from 'recoil';
|
||||
|
||||
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';
|
||||
|
||||
export const viewFieldsFamilyState = atom<
|
||||
ViewFieldDefinition<ViewFieldMetadata>[]
|
||||
>({
|
||||
key: 'viewFieldsFamilyState',
|
||||
export const viewFieldsState = atom<ViewFieldDefinition<ViewFieldMetadata>[]>({
|
||||
key: 'viewFieldsState',
|
||||
default: [],
|
||||
});
|
||||
|
||||
@ -64,18 +64,20 @@ export function TableHeader<SortField>({
|
||||
</>
|
||||
}
|
||||
displayBottomBorder={false}
|
||||
rightComponents={[
|
||||
<FilterDropdownButton
|
||||
context={TableContext}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>,
|
||||
<SortDropdownButton<SortField>
|
||||
isSortSelected={sorts.length > 0}
|
||||
availableSorts={availableSorts || []}
|
||||
onSortSelect={sortSelect}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>,
|
||||
]}
|
||||
rightComponent={
|
||||
<>
|
||||
<FilterDropdownButton
|
||||
context={TableContext}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>
|
||||
<SortDropdownButton<SortField>
|
||||
isSortSelected={sorts.length > 0}
|
||||
availableSorts={availableSorts || []}
|
||||
onSortSelect={sortSelect}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
bottomComponent={
|
||||
<SortAndFilterBar
|
||||
context={TableContext}
|
||||
|
||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
type OwnProps = {
|
||||
leftComponent?: ReactNode;
|
||||
rightComponents?: ReactNode[];
|
||||
rightComponent?: ReactNode;
|
||||
bottomComponent?: ReactNode;
|
||||
displayBottomBorder?: boolean;
|
||||
};
|
||||
@ -40,7 +40,7 @@ const StyledRightSection = styled.div`
|
||||
|
||||
export function TopBar({
|
||||
leftComponent,
|
||||
rightComponents,
|
||||
rightComponent,
|
||||
bottomComponent,
|
||||
displayBottomBorder = true,
|
||||
}: OwnProps) {
|
||||
@ -48,7 +48,7 @@ export function TopBar({
|
||||
<StyledContainer>
|
||||
<StyledTopBar displayBottomBorder={displayBottomBorder}>
|
||||
<StyledLeftSection>{leftComponent}</StyledLeftSection>
|
||||
<StyledRightSection>{rightComponents}</StyledRightSection>
|
||||
<StyledRightSection>{rightComponent}</StyledRightSection>
|
||||
</StyledTopBar>
|
||||
{bottomComponent}
|
||||
</StyledContainer>
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
type MouseListener = (positionX: number, positionY: number) => void;
|
||||
|
||||
export function useTrackPointer({
|
||||
shouldTrackPointer = true,
|
||||
onMouseMove,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
}: {
|
||||
shouldTrackPointer?: boolean;
|
||||
onMouseMove?: MouseListener;
|
||||
onMouseDown?: MouseListener;
|
||||
onMouseUp?: MouseListener;
|
||||
}) {
|
||||
const extractPosition = useCallback((event: MouseEvent | TouchEvent) => {
|
||||
const clientX =
|
||||
'clientX' in event ? event.clientX : event.changedTouches[0].clientX;
|
||||
const clientY =
|
||||
'clientY' in event ? event.clientY : event.changedTouches[0].clientY;
|
||||
|
||||
return { clientX, clientY };
|
||||
}, []);
|
||||
|
||||
const onInternalMouseMove = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const { clientX, clientY } = extractPosition(event);
|
||||
onMouseMove?.(clientX, clientY);
|
||||
},
|
||||
[onMouseMove, extractPosition],
|
||||
);
|
||||
|
||||
const onInternalMouseDown = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const { clientX, clientY } = extractPosition(event);
|
||||
onMouseDown?.(clientX, clientY);
|
||||
},
|
||||
[onMouseDown, extractPosition],
|
||||
);
|
||||
|
||||
const onInternalMouseUp = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const { clientX, clientY } = extractPosition(event);
|
||||
onMouseUp?.(clientX, clientY);
|
||||
},
|
||||
[onMouseUp, extractPosition],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldTrackPointer) {
|
||||
document.addEventListener('mousemove', onInternalMouseMove);
|
||||
document.addEventListener('mousedown', onInternalMouseDown);
|
||||
document.addEventListener('mouseup', onInternalMouseUp);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', onInternalMouseMove);
|
||||
document.removeEventListener('mousedown', onInternalMouseDown);
|
||||
document.removeEventListener('mouseup', onInternalMouseUp);
|
||||
};
|
||||
}
|
||||
}, [
|
||||
shouldTrackPointer,
|
||||
onInternalMouseMove,
|
||||
onInternalMouseDown,
|
||||
onInternalMouseUp,
|
||||
]);
|
||||
}
|
||||
@ -54,13 +54,13 @@ export function Tasks() {
|
||||
<TabList context={TasksContext} tabs={TASK_TABS} />
|
||||
</StyledTabListContainer>
|
||||
}
|
||||
rightComponents={[
|
||||
rightComponent={
|
||||
<FilterDropdownButton
|
||||
key="tasks-filter-dropdown-button"
|
||||
context={TasksContext}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TaskGroups />
|
||||
</RecoilScope>
|
||||
|
||||
Reference in New Issue
Block a user