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:
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 },
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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(() => {
|
||||||
|
|||||||
@ -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,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||||
|
|
||||||
|
export const viewPickerIsDirtyComponentState = createComponentState<boolean>({
|
||||||
|
key: 'viewPickerIsDirtyComponentState',
|
||||||
|
defaultValue: false,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user