Fix ViewPicker create mode: view type switcher (#4821)

In this PR, I'm fixing two things on the ViewPicker in Create mode:
- if the Dropdown has no max height, it should not be scrollable (which
is causing issue with inner dropdowns being cut by overflow: hidden
- if the user has changed the icon, the type or the name of the view,
consider the create form as isDirty and prevent its value to be
overriden by re-renders (cache updates for example)
This commit is contained in:
Charles Bochet
2024-04-04 18:32:55 +02:00
committed by GitHub
parent 1f98bc899d
commit 48b1be9917
6 changed files with 51 additions and 13 deletions

View File

@ -45,11 +45,17 @@ export const DropdownMenuItemsContainer = ({
}) => { }) => {
return ( return (
<StyledDropdownMenuItemsExternalContainer hasMaxHeight={hasMaxHeight}> <StyledDropdownMenuItemsExternalContainer hasMaxHeight={hasMaxHeight}>
<StyledScrollWrapper> {hasMaxHeight ? (
<StyledScrollWrapper>
<StyledDropdownMenuItemsInternalContainer>
{children}
</StyledDropdownMenuItemsInternalContainer>
</StyledScrollWrapper>
) : (
<StyledDropdownMenuItemsInternalContainer> <StyledDropdownMenuItemsInternalContainer>
{children} {children}
</StyledDropdownMenuItemsInternalContainer> </StyledDropdownMenuItemsInternalContainer>
</StyledScrollWrapper> )}
</StyledDropdownMenuItemsExternalContainer> </StyledDropdownMenuItemsExternalContainer>
); );
}; };

View File

@ -1,5 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { IconChevronLeft, IconLayoutKanban, IconTable, IconX } from 'twenty-ui'; import { IconChevronLeft, IconLayoutKanban, IconTable, IconX } from 'twenty-ui';
@ -57,6 +57,7 @@ export const ViewPickerCreateOrEditContent = () => {
viewPickerIsPersistingState, viewPickerIsPersistingState,
viewPickerKanbanFieldMetadataIdState, viewPickerKanbanFieldMetadataIdState,
viewPickerTypeState, viewPickerTypeState,
viewPickerIsDirtyState,
} = useViewPickerStates(); } = useViewPickerStates();
const [viewPickerInputName, setViewPickerInputName] = useRecoilState( const [viewPickerInputName, setViewPickerInputName] = useRecoilState(
@ -66,6 +67,7 @@ export const ViewPickerCreateOrEditContent = () => {
viewPickerSelectedIconState, viewPickerSelectedIconState,
); );
const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState); const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState);
const setViewPickerIsDirty = useSetRecoilState(viewPickerIsDirtyState);
const [viewPickerKanbanFieldMetadataId, setViewPickerKanbanFieldMetadataId] = const [viewPickerKanbanFieldMetadataId, setViewPickerKanbanFieldMetadataId] =
useRecoilState(viewPickerKanbanFieldMetadataIdState); useRecoilState(viewPickerKanbanFieldMetadataIdState);
@ -80,16 +82,13 @@ export const ViewPickerCreateOrEditContent = () => {
useScopedHotkeys( useScopedHotkeys(
Key.Enter, Key.Enter,
async () => { async () => {
if (viewPickerIsPersisting) {
return;
}
if (viewPickerMode === 'create') { if (viewPickerMode === 'create') {
if (viewPickerIsPersisting) {
return;
}
await handleCreate(); await handleCreate();
} }
if (viewPickerMode === 'edit') { if (viewPickerMode === 'edit') {
if (viewPickerIsPersisting) {
return;
}
await handleUpdate(); await handleUpdate();
} }
}, },
@ -97,6 +96,7 @@ export const ViewPickerCreateOrEditContent = () => {
); );
const onIconChange = ({ iconKey }: { iconKey: string }) => { const onIconChange = ({ iconKey }: { iconKey: string }) => {
setViewPickerIsDirty(true);
setViewPickerSelectedIcon(iconKey); setViewPickerSelectedIcon(iconKey);
}; };
@ -128,7 +128,10 @@ export const ViewPickerCreateOrEditContent = () => {
/> />
<DropdownMenuInput <DropdownMenuInput
value={viewPickerInputName} value={viewPickerInputName}
onChange={(event) => setViewPickerInputName(event.target.value)} onChange={(event) => {
setViewPickerIsDirty(true);
setViewPickerInputName(event.target.value);
}}
autoFocus autoFocus
/> />
</StyledIconAndNameContainer> </StyledIconAndNameContainer>
@ -139,7 +142,10 @@ export const ViewPickerCreateOrEditContent = () => {
label="View type" label="View type"
fullWidth fullWidth
value={viewPickerType} value={viewPickerType}
onChange={(value) => setViewPickerType(value)} onChange={(value) => {
setViewPickerIsDirty(true);
setViewPickerType(value);
}}
options={[ options={[
{ value: ViewType.Table, label: 'Table', Icon: IconTable }, { value: ViewType.Table, label: 'Table', Icon: IconTable },
{ {

View File

@ -14,6 +14,7 @@ export const ViewPickerCreateOrEditContentEffect = () => {
viewPickerIsPersistingState, viewPickerIsPersistingState,
viewPickerKanbanFieldMetadataIdState, viewPickerKanbanFieldMetadataIdState,
viewPickerTypeState, viewPickerTypeState,
viewPickerIsDirtyState,
} = useViewPickerStates(); } = useViewPickerStates();
const setViewPickerSelectedIcon = useSetRecoilState( const setViewPickerSelectedIcon = useSetRecoilState(
@ -30,6 +31,8 @@ export const ViewPickerCreateOrEditContentEffect = () => {
viewPickerReferenceViewIdState, viewPickerReferenceViewIdState,
); );
const viewPickerIsDirty = useRecoilValue(viewPickerIsDirtyState);
const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState); const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState);
const { viewsOnCurrentObject } = useGetCurrentView(); const { viewsOnCurrentObject } = useGetCurrentView();
@ -40,7 +43,11 @@ export const ViewPickerCreateOrEditContentEffect = () => {
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban(); const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
useEffect(() => { useEffect(() => {
if (isDefined(referenceView) && !viewPickerIsPersisting) { if (
isDefined(referenceView) &&
!viewPickerIsPersisting &&
!viewPickerIsDirty
) {
setViewPickerSelectedIcon(referenceView.icon); setViewPickerSelectedIcon(referenceView.icon);
setViewPickerInputName(referenceView.name); setViewPickerInputName(referenceView.name);
setViewPickerKanbanFieldMetadataId(referenceView.kanbanFieldMetadataId); setViewPickerKanbanFieldMetadataId(referenceView.kanbanFieldMetadataId);
@ -53,6 +60,7 @@ export const ViewPickerCreateOrEditContentEffect = () => {
setViewPickerSelectedIcon, setViewPickerSelectedIcon,
setViewPickerType, setViewPickerType,
viewPickerIsPersisting, viewPickerIsPersisting,
viewPickerIsDirty,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -15,6 +15,7 @@ export const useViewPickerPersistView = () => {
viewPickerReferenceViewIdState, viewPickerReferenceViewIdState,
viewPickerKanbanFieldMetadataIdState, viewPickerKanbanFieldMetadataIdState,
viewPickerTypeState, viewPickerTypeState,
viewPickerIsDirtyState,
} = useViewPickerStates(); } = useViewPickerStates();
const { createView, selectView, removeView, updateView } = useHandleViews(); const { createView, selectView, removeView, updateView } = useHandleViews();
@ -35,6 +36,7 @@ export const useViewPickerPersistView = () => {
); );
const id = v4(); const id = v4();
set(viewPickerIsPersistingState, true); set(viewPickerIsPersistingState, true);
set(viewPickerIsDirtyState, false);
await createView({ await createView({
id, id,
name, name,
@ -50,6 +52,7 @@ export const useViewPickerPersistView = () => {
createView, createView,
selectView, selectView,
viewPickerInputNameState, viewPickerInputNameState,
viewPickerIsDirtyState,
viewPickerIsPersistingState, viewPickerIsPersistingState,
viewPickerKanbanFieldMetadataIdState, viewPickerKanbanFieldMetadataIdState,
viewPickerSelectedIconState, viewPickerSelectedIconState,
@ -62,6 +65,7 @@ export const useViewPickerPersistView = () => {
async () => { async () => {
set(viewPickerIsPersistingState, true); set(viewPickerIsPersistingState, true);
closeAndResetViewPicker(); closeAndResetViewPicker();
set(viewPickerIsDirtyState, false);
const viewPickerReferenceViewId = getSnapshotValue( const viewPickerReferenceViewId = getSnapshotValue(
snapshot, snapshot,
viewPickerReferenceViewIdState, viewPickerReferenceViewIdState,
@ -78,6 +82,7 @@ export const useViewPickerPersistView = () => {
closeAndResetViewPicker, closeAndResetViewPicker,
removeView, removeView,
selectView, selectView,
viewPickerIsDirtyState,
viewPickerIsPersistingState, viewPickerIsPersistingState,
viewPickerReferenceViewIdState, viewPickerReferenceViewIdState,
viewsOnCurrentObject, viewsOnCurrentObject,
@ -88,6 +93,7 @@ export const useViewPickerPersistView = () => {
({ set, snapshot }) => ({ set, snapshot }) =>
async () => { async () => {
set(viewPickerIsPersistingState, true); set(viewPickerIsPersistingState, true);
set(viewPickerIsDirtyState, false);
closeAndResetViewPicker(); closeAndResetViewPicker();
const viewPickerReferenceViewId = getSnapshotValue( const viewPickerReferenceViewId = getSnapshotValue(
@ -112,12 +118,13 @@ export const useViewPickerPersistView = () => {
}, },
[ [
viewPickerIsPersistingState, viewPickerIsPersistingState,
viewPickerIsDirtyState,
closeAndResetViewPicker,
viewPickerReferenceViewIdState, viewPickerReferenceViewIdState,
viewPickerInputNameState, viewPickerInputNameState,
viewPickerSelectedIconState, viewPickerSelectedIconState,
updateView, updateView,
selectView, selectView,
closeAndResetViewPicker,
], ],
); );

View File

@ -1,6 +1,7 @@
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState'; import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState'; import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState'; import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
import { viewPickerModeComponentState } from '@/views/view-picker/states/viewPickerModeComponentState'; import { viewPickerModeComponentState } from '@/views/view-picker/states/viewPickerModeComponentState';
@ -46,5 +47,9 @@ export const useViewPickerStates = (viewComponentId?: string) => {
viewPickerTypeComponentState, viewPickerTypeComponentState,
componentId, componentId,
), ),
viewPickerIsDirtyState: extractComponentState(
viewPickerIsDirtyComponentState,
componentId,
),
}; };
}; };

View File

@ -0,0 +1,6 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const viewPickerIsDirtyComponentState = createComponentState<boolean>({
key: 'viewPickerIsDirtyComponentState',
defaultValue: false,
});