Improve viewbar api (#2233)

* create scopes

* fix import bug

* add useView hook

* wip

* wip

* currentViewId is now retrieved via useView

* working on sorts with useView

* refactor in progress

* refactor in progress

* refactor in progress

* refactor in progress

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fix code

* fix code

* wip

* push

* Fix issue dependencies

* Fix resize

---------

Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
This commit is contained in:
Charles Bochet
2023-10-27 10:52:26 +02:00
committed by GitHub
parent 6a72c14af3
commit 5ba68e997d
205 changed files with 3092 additions and 3249 deletions

View File

@ -1,123 +0,0 @@
import { useContext } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { ViewBar } from '@/ui/data/view-bar/components/ViewBar';
import { ViewBarContext } from '@/ui/data/view-bar/contexts/ViewBarContext';
import { currentViewIdScopedState } from '@/ui/data/view-bar/states/currentViewIdScopedState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { boardColumnsState } from '../states/boardColumnsState';
import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
import { savedBoardColumnsState } from '../states/savedBoardColumnsState';
import { canPersistBoardCardFieldsScopedFamilySelector } from '../states/selectors/canPersistBoardCardFieldsScopedFamilySelector';
import { canPersistBoardColumnsSelector } from '../states/selectors/canPersistBoardColumnsSelector';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
import { BoardScopeIds } from '../types/enums/BoardScopeIds';
import { BoardOptionsDropdown } from './BoardOptionsDropdown';
export type BoardHeaderProps = {
className?: string;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
};
export const BoardHeader = ({ className, onStageAdd }: BoardHeaderProps) => {
const { onCurrentViewSubmit, ...viewBarContextProps } =
useContext(ViewBarContext);
const BoardRecoilScopeContext =
useContext(BoardContext).BoardRecoilScopeContext;
const ViewBarRecoilScopeContext =
useContext(ViewBarContext).ViewBarRecoilScopeContext;
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
const currentViewId = useRecoilScopedValue(
currentViewIdScopedState,
ViewBarRecoilScopeContext,
);
const canPersistBoardCardFields = useRecoilValue(
canPersistBoardCardFieldsScopedFamilySelector({
recoilScopeId: boardRecoilScopeId,
viewId: currentViewId,
}),
);
const canPersistBoardColumns = useRecoilValue(canPersistBoardColumnsSelector);
const [boardCardFields, setBoardCardFields] = useRecoilScopedState(
boardCardFieldsScopedState,
BoardRecoilScopeContext,
);
const [savedBoardCardFields, setSavedBoardCardFields] = useRecoilState(
savedBoardCardFieldsFamilyState(currentViewId),
);
const [_, setSearchParams] = useSearchParams();
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
const [, setSavedBoardColumns] = useRecoilState(savedBoardColumnsState);
const savedBoardColumns = useRecoilValue(savedBoardColumnsState);
const handleViewBarReset = () => {
setBoardCardFields(savedBoardCardFields);
setBoardColumns(savedBoardColumns);
};
const handleViewSelect = useRecoilCallback(
({ set, snapshot }) =>
async (viewId: string) => {
const savedBoardCardFields = await snapshot.getPromise(
savedBoardCardFieldsFamilyState(viewId),
);
set(
boardCardFieldsScopedState(boardRecoilScopeId),
savedBoardCardFields,
);
setSearchParams({ view: viewId });
},
[boardRecoilScopeId, setSearchParams],
);
const handleCurrentViewSubmit = async () => {
if (canPersistBoardCardFields) {
setSavedBoardCardFields(boardCardFields);
}
if (canPersistBoardColumns) {
setSavedBoardColumns(boardColumns);
}
await onCurrentViewSubmit?.();
};
const canPersistView = canPersistBoardCardFields || canPersistBoardColumns;
return (
<ViewBarContext.Provider
value={{
...viewBarContextProps,
canPersistViewFields: canPersistView,
onCurrentViewSubmit: handleCurrentViewSubmit,
onViewBarReset: handleViewBarReset,
onViewSelect: handleViewSelect,
}}
>
<ViewBar
className={className}
optionsDropdownButton={
<BoardOptionsDropdown
customHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onStageAdd={onStageAdd}
/>
}
optionsDropdownScopeId={BoardScopeIds.OptionsDropdown}
/>
</ViewBarContext.Provider>
);
};

View File

@ -1,6 +1,4 @@
import { useResetRecoilState } from 'recoil';
import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState';
import { useView } from '@/views/hooks/useView';
import { Dropdown } from '../../dropdown/components/Dropdown';
import { DropdownScope } from '../../dropdown/scopes/DropdownScope';
@ -21,7 +19,7 @@ export const BoardOptionsDropdown = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownProps) => {
const resetViewEditMode = useResetRecoilState(viewEditModeState);
const { setViewEditMode } = useView();
return (
<DropdownScope dropdownScopeId={BoardScopeIds.OptionsDropdown}>
@ -34,7 +32,7 @@ export const BoardOptionsDropdown = ({
/>
}
dropdownHotkeyScope={customHotkeyScope}
onClickOutside={resetViewEditMode}
onClickOutside={() => setViewEditMode('none')}
dropdownMenuWidth={170}
/>
</DropdownScope>

View File

@ -1,19 +1,9 @@
import { useContext, useRef, useState } from 'react';
import {
useRecoilCallback,
useRecoilState,
useRecoilValue,
useResetRecoilState,
} from 'recoil';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { ViewFieldsVisibilityDropdownSection } from '@/ui/data/view-bar/components/ViewFieldsVisibilityDropdownSection';
import { useUpsertView } from '@/ui/data/view-bar/hooks/useUpsertView';
import { currentViewScopedSelector } from '@/ui/data/view-bar/states/selectors/currentViewScopedSelector';
import { viewsByIdScopedSelector } from '@/ui/data/view-bar/states/selectors/viewsByIdScopedSelector';
import { viewEditModeState } from '@/ui/data/view-bar/states/viewEditModeState';
import {
IconBaselineDensitySmall,
IconChevronLeft,
@ -35,6 +25,10 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { viewEditModeScopedState } from '@/views/states/viewEditModeScopedState';
import { useBoardCardFields } from '../hooks/useBoardCardFields';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
@ -63,6 +57,8 @@ export const BoardOptionsDropdownContent = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownContentProps) => {
const { setViewEditMode, createView, currentViewId } = useView();
const { viewEditMode, currentView } = useViewInternalStates();
const { BoardRecoilScopeContext } = useContext(BoardContext);
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
@ -90,17 +86,6 @@ export const BoardOptionsDropdownContent = ({
);
const hasVisibleFields = visibleBoardCardFields.length > 0;
const viewsById = useRecoilScopedValue(
viewsByIdScopedSelector,
BoardRecoilScopeContext, // TODO: replace with ViewBarRecoilScopeContext
);
const currentView = useRecoilScopedValue(
currentViewScopedSelector,
BoardRecoilScopeContext,
);
const viewEditMode = useRecoilValue(viewEditModeState);
const resetViewEditMode = useResetRecoilState(viewEditModeState);
const handleStageSubmit = () => {
if (currentMenu !== 'stage-creation' || !stageInputRef?.current?.value)
return;
@ -119,23 +104,28 @@ export const BoardOptionsDropdownContent = ({
onStageAdd?.(columnToCreate);
};
const { upsertView } = useUpsertView();
const handleViewNameSubmit = useRecoilCallback(
({ set, snapshot }) =>
async () => {
const viewEditMode = snapshot
.getLoadable(viewEditModeScopedState({ scopeId: boardRecoilScopeId }))
.getValue();
if (!viewEditMode) {
return;
}
const boardCardFields = await snapshot.getPromise(
boardCardFieldsScopedState(boardRecoilScopeId),
);
const isCreateMode = viewEditMode.mode === 'create';
const isCreateMode = viewEditMode === 'create';
const name = viewEditInputRef.current?.value;
const view = await upsertView(name);
if (view && isCreateMode) {
set(savedBoardCardFieldsFamilyState(view.id), boardCardFields);
if (isCreateMode && name) {
await createView(name);
set(savedBoardCardFieldsFamilyState(currentViewId), boardCardFields);
}
},
[boardRecoilScopeId, upsertView, viewEditMode.mode],
[boardRecoilScopeId, createView, currentViewId],
);
const resetMenu = () => setCurrentMenu(undefined);
@ -152,7 +142,7 @@ export const BoardOptionsDropdownContent = ({
useScopedHotkeys(
Key.Escape,
() => {
resetViewEditMode();
setViewEditMode('none');
closeDropdown();
},
customHotkeyScope.scope,
@ -163,7 +153,6 @@ export const BoardOptionsDropdownContent = ({
() => {
handleStageSubmit();
handleViewNameSubmit();
resetViewEditMode();
closeDropdown();
},
customHotkeyScope.scope,
@ -173,20 +162,26 @@ export const BoardOptionsDropdownContent = ({
<>
{!currentMenu && (
<>
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode.mode === 'create' || !!viewEditMode.viewId}
placeholder={
viewEditMode.mode === 'create' ? 'New view' : 'View name'
}
defaultValue={
viewEditMode.mode === 'create'
? ''
: viewEditMode.viewId
? viewsById[viewEditMode.viewId]?.name
: currentView?.name
}
/>
{viewEditMode && (
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode !== 'none'}
placeholder={
viewEditMode === 'create'
? 'New view'
: viewEditMode === 'edit'
? 'View name'
: ''
}
defaultValue={
viewEditMode === 'create'
? ''
: viewEditMode === 'edit'
? currentView?.name
: ''
}
/>
)}
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItemNavigate

View File

@ -6,7 +6,6 @@ import { useRecoilValue } from 'recoil';
import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { BoardHeader } from '@/ui/layout/board/components/BoardHeader';
import { StyledBoard } from '@/ui/layout/board/components/StyledBoard';
import { BoardColumnContext } from '@/ui/layout/board/contexts/BoardColumnContext';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
@ -44,14 +43,13 @@ const StyledWrapper = styled.div`
width: 100%;
`;
const StyledBoardHeader = styled(BoardHeader)`
const StyledBoardHeader = styled.div`
position: relative;
z-index: 1;
` as typeof BoardHeader;
`;
export const EntityBoard = ({
boardOptions,
onColumnAdd,
onColumnDelete,
onEditColumnTitle,
}: EntityBoardProps) => {
@ -147,7 +145,7 @@ export const EntityBoard = ({
return (boardColumns?.length ?? 0) > 0 ? (
<StyledWrapper>
<StyledBoardHeader onStageAdd={onColumnAdd} />
<StyledBoardHeader />
<ScrollWrapper>
<StyledBoard ref={boardRef}>
<DragDropContext onDragEnd={onDragEnd}>

View File

@ -1,53 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/testing-library';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
import { ViewBarContext } from '@/ui/data/view-bar/contexts/ViewBarContext';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { BoardOptionsDropdown } from '../BoardOptionsDropdown';
const meta: Meta<typeof BoardOptionsDropdown> = {
title: 'UI/Layout/Board/Options/BoardOptionsDropdown',
component: BoardOptionsDropdown,
decorators: [
(Story, { parameters }) => (
<BoardContext.Provider
value={{
BoardRecoilScopeContext: parameters.customRecoilScopeContext,
}}
>
<ViewBarContext.Provider
value={{
ViewBarRecoilScopeContext: parameters.customRecoilScopeContext,
}}
>
<Story />
</ViewBarContext.Provider>
</BoardContext.Provider>
),
ComponentWithRecoilScopeDecorator,
ComponentDecorator,
],
parameters: {
customRecoilScopeContext: CompanyBoardRecoilScopeContext,
},
args: {
customHotkeyScope: { scope: 'scope' },
},
};
export default meta;
type Story = StoryObj<typeof BoardOptionsDropdown>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const dropdownButton = canvas.getByText('Options');
await userEvent.click(dropdownButton);
},
};

View File

@ -1,5 +1,5 @@
import { ViewFieldForVisibility } from '@/ui/data/view-bar/types/ViewFieldForVisibility';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { ViewFieldForVisibility } from '@/views/types/ViewFieldForVisibility';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';

View File

@ -1,6 +1,6 @@
import { useRecoilState } from 'recoil';
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
import { useMoveViewColumns } from '@/ui/data/data-table/hooks/useMoveViewColumns';
import { useUpdatePipelineStageMutation } from '~/generated/graphql';
import { boardColumnsState } from '../states/boardColumnsState';

View File

@ -1,7 +1,7 @@
import { ComponentType } from 'react';
import { FilterDefinitionByEntity } from '@/ui/data/view-bar/types/FilterDefinitionByEntity';
import { SortDefinition } from '@/ui/data/view-bar/types/SortDefinition';
import { FilterDefinitionByEntity } from '@/ui/data/filter/types/FilterDefinitionByEntity';
import { SortDefinition } from '@/ui/data/sort/types/SortDefinition';
import { PipelineProgress } from '~/generated/graphql';
export type BoardOptions = {

View File

@ -56,11 +56,11 @@ export const useDropdown = (props?: UseDropdownProps) => {
};
return {
scopeId,
isDropdownOpen: isDropdownOpen,
closeDropdown: closeDropdownButton,
toggleDropdown: toggleDropdownButton,
openDropdown: openDropdownButton,
scopeId,
dropdownHotkeyScope,
setDropdownHotkeyScope,
dropdownWidth,