Feat: The scrollbar should fade away when the scroll is finished or not started (#1269)
* The scrollbar should fade away when the scroll is finished or not started Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: FellipeMTX <fellipefacdir@gmail.com> * Complete scroll work * Fix pr * Fix pr --------- Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: FellipeMTX <fellipefacdir@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -14,6 +14,8 @@ import { BoardColumnIdContext } from '@/ui/board/contexts/BoardColumnIdContext';
|
|||||||
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
|
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
import { StyledScrollWrapper } from '@/ui/utilities/scroll/components/StyledScrollWrapper';
|
||||||
|
import { useListenScroll } from '@/ui/utilities/scroll/hooks/useListenScroll';
|
||||||
import {
|
import {
|
||||||
PipelineProgress,
|
PipelineProgress,
|
||||||
PipelineProgressOrderByWithRelationInput,
|
PipelineProgressOrderByWithRelationInput,
|
||||||
@ -29,11 +31,8 @@ import { BoardOptions } from '../types/BoardOptions';
|
|||||||
|
|
||||||
import { EntityBoardColumn } from './EntityBoardColumn';
|
import { EntityBoardColumn } from './EntityBoardColumn';
|
||||||
|
|
||||||
const StyledBoardWithHeader = styled.div`
|
const StyledCustomScrollWrapper = styled(StyledScrollWrapper)`
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function EntityBoard({
|
export function EntityBoard({
|
||||||
@ -104,10 +103,15 @@ export function EntityBoard({
|
|||||||
return a.index - b.index;
|
return a.index - b.index;
|
||||||
});
|
});
|
||||||
|
|
||||||
const boardRef = useRef(null);
|
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||||
|
const boardRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useListenScroll({
|
||||||
|
scrollableRef,
|
||||||
|
});
|
||||||
|
|
||||||
return (boardColumns?.length ?? 0) > 0 ? (
|
return (boardColumns?.length ?? 0) > 0 ? (
|
||||||
<StyledBoardWithHeader>
|
<StyledCustomScrollWrapper ref={scrollableRef}>
|
||||||
<BoardHeader
|
<BoardHeader
|
||||||
viewName="All opportunities"
|
viewName="All opportunities"
|
||||||
viewIcon={<IconList size={theme.icon.size.md} />}
|
viewIcon={<IconList size={theme.icon.size.md} />}
|
||||||
@ -115,7 +119,7 @@ export function EntityBoard({
|
|||||||
onSortsUpdate={updateSorts}
|
onSortsUpdate={updateSorts}
|
||||||
context={CompanyBoardRecoilScopeContext}
|
context={CompanyBoardRecoilScopeContext}
|
||||||
/>
|
/>
|
||||||
<StyledBoard ref={boardRef}>
|
<StyledBoard>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
{sortedBoardColumns.map((column) => (
|
{sortedBoardColumns.map((column) => (
|
||||||
<BoardColumnIdContext.Provider value={column.id} key={column.id}>
|
<BoardColumnIdContext.Provider value={column.id} key={column.id}>
|
||||||
@ -137,7 +141,7 @@ export function EntityBoard({
|
|||||||
dragSelectable={boardRef}
|
dragSelectable={boardRef}
|
||||||
onDragSelectionChange={setCardSelected}
|
onDragSelectionChange={setCardSelected}
|
||||||
/>
|
/>
|
||||||
</StyledBoardWithHeader>
|
</StyledCustomScrollWrapper>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const StyledLayout = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
background-color: ${({ theme }) => theme.border.color.medium};
|
background-color: transparent;
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -8,7 +8,6 @@ const StyledPanel = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import type {
|
|||||||
import { SortType } from '@/ui/filter-n-sort/types/interface';
|
import { SortType } from '@/ui/filter-n-sort/types/interface';
|
||||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
import { StyledScrollWrapper } from '@/ui/utilities/scroll/components/StyledScrollWrapper';
|
||||||
|
import { useListenScroll } from '@/ui/utilities/scroll/hooks/useListenScroll';
|
||||||
|
|
||||||
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||||
@ -87,11 +89,6 @@ const StyledTableContainer = styled.div`
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTableWrapper = styled.div`
|
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type OwnProps<SortField> = {
|
type OwnProps<SortField> = {
|
||||||
viewName: string;
|
viewName: string;
|
||||||
viewIcon?: React.ReactNode;
|
viewIcon?: React.ReactNode;
|
||||||
@ -128,6 +125,12 @@ export function EntityTable<SortField>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const scrollableRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useListenScroll({
|
||||||
|
scrollableRef,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityUpdateMutationContext.Provider value={updateEntityMutation}>
|
<EntityUpdateMutationContext.Provider value={updateEntityMutation}>
|
||||||
<StyledTableWithHeader>
|
<StyledTableWithHeader>
|
||||||
@ -140,12 +143,12 @@ export function EntityTable<SortField>({
|
|||||||
onViewSubmit={onViewSubmit}
|
onViewSubmit={onViewSubmit}
|
||||||
onImport={onImport}
|
onImport={onImport}
|
||||||
/>
|
/>
|
||||||
<StyledTableWrapper>
|
<StyledScrollWrapper ref={scrollableRef}>
|
||||||
<StyledTable>
|
<StyledTable>
|
||||||
<EntityTableHeader onColumnsChange={onColumnsChange} />
|
<EntityTableHeader onColumnsChange={onColumnsChange} />
|
||||||
<EntityTableBody />
|
<EntityTableBody />
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
</StyledTableWrapper>
|
</StyledScrollWrapper>
|
||||||
<DragSelect
|
<DragSelect
|
||||||
dragSelectable={tableBodyRef}
|
dragSelectable={tableBodyRef}
|
||||||
onDragSelectionStart={resetTableRowSelection}
|
onDragSelectionStart={resetTableRowSelection}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
export const StyledScrollWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.scrolling::-webkit-scrollbar-thumb {
|
||||||
|
background-color: ${({ theme }) => theme.border.color.medium};
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import debounce from 'lodash.debounce';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { isScrollingState } from '../states/isScrollingState';
|
||||||
|
|
||||||
|
export function useListenScroll<T extends Element>({
|
||||||
|
scrollableRef,
|
||||||
|
}: {
|
||||||
|
scrollableRef: React.RefObject<T>;
|
||||||
|
}) {
|
||||||
|
const hideScrollBarsCallback = useRecoilCallback(({ snapshot }) => () => {
|
||||||
|
const isScrolling = snapshot.getLoadable(isScrollingState).getValue();
|
||||||
|
if (!isScrolling) {
|
||||||
|
scrollableRef.current?.classList.remove('scrolling');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleScrollStart = useRecoilCallback(({ set }) => () => {
|
||||||
|
set(isScrollingState, true);
|
||||||
|
scrollableRef.current?.classList.add('scrolling');
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleScrollEnd = useRecoilCallback(({ set }) => () => {
|
||||||
|
set(isScrollingState, false);
|
||||||
|
debounce(hideScrollBarsCallback, 1000)();
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const refTarget = scrollableRef.current;
|
||||||
|
|
||||||
|
refTarget?.addEventListener('scrollend', handleScrollEnd);
|
||||||
|
refTarget?.addEventListener('scroll', handleScrollStart);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
refTarget?.removeEventListener('scrollend', handleScrollEnd);
|
||||||
|
refTarget?.removeEventListener('scroll', handleScrollStart);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
hideScrollBarsCallback,
|
||||||
|
handleScrollStart,
|
||||||
|
handleScrollEnd,
|
||||||
|
scrollableRef,
|
||||||
|
]);
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const isScrollingState = atom({
|
||||||
|
key: 'scroll/isScollingState',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -49,6 +48,7 @@ export function Companies() {
|
|||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
linkedinUrl: '',
|
linkedinUrl: '',
|
||||||
|
idealCustomerProfile: false,
|
||||||
employees: null,
|
employees: null,
|
||||||
idealCustomerProfile: false,
|
idealCustomerProfile: false,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user