Skip to content

Commit

Permalink
feat: add pending state for clicking builder tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
karrui committed Aug 25, 2022
1 parent 173ed2a commit 490a4dd
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const useDirtyModal = () => {
handleDesignClick,
pendingTab,
movePendingToActiveTab,
clearPendingTab,
} = useCreatePageSidebar()

const { designHoldingState, designMoveFromHolding, designClearHoldingState } =
Expand Down Expand Up @@ -59,7 +60,7 @@ export const useDirtyModal = () => {
} else if (designHoldingState !== null) {
designMoveFromHolding()
handleDesignClick()
} else if (pendingTab !== null) {
} else if (pendingTab !== undefined) {
movePendingToActiveTab()
}
}, [
Expand All @@ -78,19 +79,23 @@ export const useDirtyModal = () => {
builderClearHoldingStateData()
} else if (designHoldingState !== null) {
designClearHoldingState()
} else if (pendingTab !== undefined) {
clearPendingTab()
}
}, [
builderClearHoldingStateData,
builderHoldingStateData,
clearPendingTab,
designClearHoldingState,
designHoldingState,
pendingTab,
])

const isOpen = useMemo(
() =>
builderHoldingStateData !== null ||
designHoldingState !== null ||
pendingTab !== null,
pendingTab !== undefined,
[builderHoldingStateData, designHoldingState, pendingTab],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export const BuilderAndDesignContent = ({
),
)

useEffect(() => setFieldsToInactive, [setFieldsToInactive])
useEffect(() => {
setFieldsToInactive()
}, [setFieldsToInactive])

return (
<Flex flex={1} overflow="auto">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { BiX } from 'react-icons/bi'
import { CloseButton } from '@chakra-ui/react'

import { useCreatePageSidebar } from '../../common/CreatePageSidebarContext'
import { isDirtySelector, useFieldBuilderStore } from '../useFieldBuilderStore'

export const CreatePageDrawerCloseButton = (): JSX.Element => {
const isDirty = useFieldBuilderStore(isDirtySelector)
const { handleClose } = useCreatePageSidebar()

const handleCloseDrawer = useCallback(() => {
handleClose()
}, [handleClose])
handleClose(isDirty)
}, [handleClose, isDirty])

return (
<CloseButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export const DesignInput = (): JSX.Element | null => {
estTimeTaken: estTimeTakenTransformed,
...rest,
},
{ onSuccess: handleClose },
{ onSuccess: () => handleClose() },
)
} else {
const customLogoMeta = await handleUploadLogo(attachment)
Expand All @@ -193,7 +193,7 @@ export const DesignInput = (): JSX.Element | null => {
estTimeTaken: estTimeTakenTransformed,
...rest,
},
{ onSuccess: handleClose },
{ onSuccess: () => handleClose() },
)
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export const EndPageBuilderInput = ({
variant="clear"
colorScheme="secondary"
isDisabled={endPageMutation.isLoading}
onClick={closeBuilderDrawer}
onClick={() => closeBuilderDrawer()}
>
Cancel
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const FormFieldDrawerActions = ({
isFullWidth={isMobile}
variant="clear"
colorScheme="secondary"
onClick={handleCancel}
onClick={() => handleCancel()}
>
Cancel
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ export const useEditFieldForm = <FormShape, FieldShape extends FormField>({
[stateData, updateCreateState, updateEditState],
)

const handleCancel = useCallback(() => {
setToInactive()
}, [setToInactive])

useDebounce(
() => handleChange(transform.output(clonedWatchedInputs, field)),
300,
Expand All @@ -183,7 +187,7 @@ export const useEditFieldForm = <FormShape, FieldShape extends FormField>({
formMethods: editForm,
buttonText,
handleUpdateField,
handleCancel: setToInactive,
handleCancel,
isLoading: createFieldMutation.isLoading || editFieldMutation.isLoading,
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ReactNode } from 'react'
import { ReactNode, useCallback } from 'react'
import { BiLeftArrowAlt } from 'react-icons/bi'
import { Stack, Text } from '@chakra-ui/react'

import IconButton from '~components/IconButton'

import {
isDirtySelector,
setToInactiveSelector,
useFieldBuilderStore,
} from '../../useFieldBuilderStore'
Expand All @@ -19,8 +20,13 @@ export const BuilderDrawerContainer = ({
title,
children,
}: BuilderDrawerContainerProps): JSX.Element | null => {
const isDirty = useFieldBuilderStore(isDirtySelector)
const setToInactive = useFieldBuilderStore(setToInactiveSelector)

const handleBack = useCallback(() => {
setToInactive(isDirty)
}, [isDirty, setToInactive])

return (
<>
<Stack
Expand All @@ -40,7 +46,7 @@ export const BuilderDrawerContainer = ({
aria-label="Back to field selection"
variant="clear"
colorScheme="secondary"
onClick={setToInactive}
onClick={handleBack}
icon={<BiLeftArrowAlt />}
/>
<Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@ export type FieldBuilderStore = {
) => void
updateEditState: (field: FormFieldDto, holding?: boolean) => void
setEditEndPage: (holding?: boolean) => void
setToInactive: () => void
setToInactive: (holding?: boolean) => void
isDirty: boolean
setIsDirty: (isDirty: boolean) => void
stateData:
| FieldBuilderCreateEditStateData
| { state: FieldBuilderState.Inactive }
// Used when there is a dirty state and we want to hold the next state to be set.
// Will be used to set stateData if user confirms discarding changes.
holdingStateData: FieldBuilderCreateEditStateData | null
holdingStateData:
| FieldBuilderCreateEditStateData
| { state: FieldBuilderState.Inactive }
| null
clearHoldingStateData: () => void
moveFromHolding: () => void
}
Expand Down Expand Up @@ -109,8 +112,15 @@ export const useFieldBuilderStore = create<FieldBuilderStore>(
set({ stateData: nextState })
}
},
setToInactive: () => {
set({ stateData: { state: FieldBuilderState.Inactive } })
setToInactive: (holding?: boolean) => {
const nextState: FieldBuilderStore['holdingStateData'] = {
state: FieldBuilderState.Inactive,
}
if (holding) {
set({ holdingStateData: nextState })
} else {
set({ stateData: nextState })
}
},
})),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '~features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext'

import {
isDirtySelector,
setToInactiveSelector,
useFieldBuilderStore,
} from '../../builder-and-design/useFieldBuilderStore'
Expand All @@ -22,6 +23,7 @@ import { DrawerTabIcon } from './DrawerTabIcon'
export const CreatePageSidebar = (): JSX.Element | null => {
const isMobile = useIsMobile()
const setFieldsToInactive = useFieldBuilderStore(setToInactiveSelector)
const isDirty = useFieldBuilderStore(isDirtySelector)
const { activeTab, handleBuilderClick, handleDesignClick, handleLogicClick } =
useCreatePageSidebar()

Expand All @@ -30,8 +32,18 @@ export const CreatePageSidebar = (): JSX.Element | null => {
if (isMobile) {
setFieldsToInactive()
}
handleBuilderClick()
}, [handleBuilderClick, isMobile, setFieldsToInactive])
handleBuilderClick(isDirty)
}, [handleBuilderClick, isDirty, isMobile, setFieldsToInactive])

const handleDrawerDesignClick = useCallback(
() => handleDesignClick(isDirty),
[handleDesignClick, isDirty],
)

const handleDrawerLogicClick = useCallback(
() => handleLogicClick(isDirty),
[handleLogicClick, isDirty],
)

return (
<Stack
Expand All @@ -55,14 +67,14 @@ export const CreatePageSidebar = (): JSX.Element | null => {
<DrawerTabIcon
label="Design your form"
icon={<BxsColorFill fontSize="1.5rem" />}
onClick={handleDesignClick}
onClick={handleDrawerDesignClick}
isActive={activeTab === DrawerTabs.Design}
id={FEATURE_TOUR[1].id}
/>
<DrawerTabIcon
label="Add conditional logic"
icon={<BiGitMerge fontSize="1.5rem" />}
onClick={handleLogicClick}
onClick={handleDrawerLogicClick}
isActive={activeTab === DrawerTabs.Logic}
id={FEATURE_TOUR[2].id}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
useDesignStore,
} from '../../builder-and-design/useDesignStore'
import {
isDirtySelector,
setToInactiveSelector,
useFieldBuilderStore,
} from '../../builder-and-design/useFieldBuilderStore'
Expand All @@ -30,13 +29,14 @@ export enum DrawerTabs {

type CreatePageSidebarContextProps = {
activeTab: DrawerTabs | null
pendingTab: DrawerTabs | null
pendingTab?: DrawerTabs | null
movePendingToActiveTab: () => void
handleBuilderClick: () => void
handleDesignClick: () => void
handleLogicClick: () => void
clearPendingTab: () => void
handleBuilderClick: (shouldBePending?: boolean) => void
handleDesignClick: (shouldBePending?: boolean) => void
handleLogicClick: (shouldBePending?: boolean) => void
handleClose: (shouldBePending?: boolean) => void
isDrawerOpen: boolean
handleClose: () => void
fieldListTabIndex: FieldListTabIndex
setFieldListTabIndex: (tabIndex: FieldListTabIndex) => void
}
Expand All @@ -60,13 +60,15 @@ export const useCreatePageSidebarContext =
const isMobile = useIsMobile()
const [activeTab, setActiveTab] = useState<DrawerTabs | null>(null)
// Any pending tab due to unsaved changes.
const [pendingTab, setPendingTab] = useState<DrawerTabs | null>(null)
// Pending tab can be `null` if the next tab state is to be closed.
const [pendingTab, setPendingTab] = useState<
DrawerTabs | null | undefined
>()
const isDrawerOpen = useMemo(
() => activeTab !== null && activeTab !== DrawerTabs.Logic,
[activeTab],
)
const setFieldsToInactive = useFieldBuilderStore(setToInactiveSelector)
const isDirty = useFieldBuilderStore(isDirtySelector)
const setDesignState = useDesignStore(setStateSelector)

const [fieldListTabIndex, setFieldListTabIndex] =
Expand All @@ -83,48 +85,62 @@ export const useCreatePageSidebarContext =
if (activeTab !== DrawerTabs.Design) setDesignState(DesignState.Inactive)
}, [activeTab, setDesignState])

const setPendingTabIfDirty = useCallback(
(tab: DrawerTabs) => {
if (isDirty) {
const setActiveOrPendingTab = useCallback(
(tab: DrawerTabs | null, shouldBePending?: boolean) => {
if (shouldBePending) {
setPendingTab(tab)
} else {
setActiveTab(tab)
if (tab === null && !isMobile) {
setFieldsToInactive()
}
}
},
[isDirty],
[isMobile, setFieldsToInactive],
)

const clearPendingTab = useCallback(() => {
setPendingTab(undefined)
}, [])

const handleBuilderClick = useCallback(
() => setPendingTabIfDirty(DrawerTabs.Builder),
[setPendingTabIfDirty],
(shouldBePending?: boolean) =>
setActiveOrPendingTab(DrawerTabs.Builder, shouldBePending),
[setActiveOrPendingTab],
)

const handleDesignClick = useCallback(
() => setPendingTabIfDirty(DrawerTabs.Design),
[setPendingTabIfDirty],
(shouldBePending?: boolean) =>
setActiveOrPendingTab(DrawerTabs.Design, shouldBePending),
[setActiveOrPendingTab],
)

const handleLogicClick = useCallback(
() => setPendingTabIfDirty(DrawerTabs.Logic),
[setPendingTabIfDirty],
(shouldBePending?: boolean) =>
setActiveOrPendingTab(DrawerTabs.Logic, shouldBePending),
[setActiveOrPendingTab],
)

const handleClose = useCallback(() => {
if (!isMobile) {
setFieldsToInactive()
}
setActiveTab(null)
}, [isMobile, setFieldsToInactive])
const handleClose = useCallback(
(shouldBePending?: boolean) => {
setActiveOrPendingTab(null, shouldBePending)
},
[setActiveOrPendingTab],
)

const movePendingToActiveTab = useCallback(() => {
if (pendingTab === null) return
if (pendingTab === undefined) return
setActiveTab(pendingTab)
setPendingTab(null)
}, [pendingTab, setActiveTab, setPendingTab])
if (pendingTab === null && !isMobile) {
setFieldsToInactive()
}
setPendingTab(undefined)
}, [isMobile, pendingTab, setFieldsToInactive])

return {
activeTab,
pendingTab,
clearPendingTab,
movePendingToActiveTab,
isDrawerOpen,
handleBuilderClick,
Expand Down

0 comments on commit 490a4dd

Please sign in to comment.