Skip to content

Commit 738fc20

Browse files
authored
Merge pull request #217 from hiveteams/feat/navigation
Feat/navigation
2 parents 6a3168a + e401fb1 commit 738fc20

File tree

16 files changed

+142
-61
lines changed

16 files changed

+142
-61
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ Common date-time formats can be viewed [here](https://docs.sheetjs.com/docs/csf/
207207
autoMapSelectValues?: boolean
208208
// Headers matching accuracy: 1 for strict and up for more flexible matching. Default: 2
209209
autoMapDistance?: number
210+
// Enable navigation in stepper component and show back button. Default: false
211+
isNavigationEnabled?: boolean
210212
```
211213

212214
## Customisation

src/components/ContinueButton.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
1-
import { Button, ModalFooter } from "@chakra-ui/react"
1+
import { Button, ModalFooter, useStyleConfig } from "@chakra-ui/react"
2+
import { themeOverrides } from "../theme"
23

34
type ContinueButtonProps = {
45
onContinue: (val: any) => void
6+
onBack?: () => void
57
title: string
8+
backTitle?: string
69
isLoading?: boolean
710
}
811

9-
export const ContinueButton = ({ onContinue, title, isLoading }: ContinueButtonProps) => (
10-
<ModalFooter>
11-
<Button size="lg" w="21rem" onClick={onContinue} isLoading={isLoading}>
12-
{title}
13-
</Button>
14-
</ModalFooter>
15-
)
12+
export const ContinueButton = ({ onContinue, onBack, title, backTitle, isLoading }: ContinueButtonProps) => {
13+
const styles = useStyleConfig("Modal") as (typeof themeOverrides)["components"]["Modal"]["baseStyle"]
14+
return (
15+
<ModalFooter justifyContent={onBack && "space-between"}>
16+
{onBack && (
17+
<Button size="lg" w="21rem" sx={styles.backButton} onClick={onBack} isLoading={isLoading}>
18+
{backTitle}
19+
</Button>
20+
)}
21+
<Button size="lg" w="21rem" sx={styles.continueButton} onClick={onContinue} isLoading={isLoading}>
22+
{title}
23+
</Button>
24+
</ModalFooter>
25+
)
26+
}

src/hooks/useRsiInitialStep.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/steps/MatchColumnsStep/MatchColumnsStep.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type MatchColumnsProps<T extends string> = {
1717
data: RawData[]
1818
headerValues: RawData
1919
onContinue: (data: any[], rawData: RawData[], columns: Columns<T>) => void
20+
onBack?: () => void
2021
}
2122

2223
export enum ColumnType {
@@ -62,7 +63,12 @@ export type Column<T extends string> =
6263

6364
export type Columns<T extends string> = Column<T>[]
6465

65-
export const MatchColumnsStep = <T extends string>({ data, headerValues, onContinue }: MatchColumnsProps<T>) => {
66+
export const MatchColumnsStep = <T extends string>({
67+
data,
68+
headerValues,
69+
onContinue,
70+
onBack,
71+
}: MatchColumnsProps<T>) => {
6672
const toast = useToast()
6773
const dataExample = data.slice(0, 2)
6874
const { fields, autoMapHeaders, autoMapSelectValues, autoMapDistance, translations } = useRsi<T>()
@@ -173,6 +179,7 @@ export const MatchColumnsStep = <T extends string>({ data, headerValues, onConti
173179
<ColumnGrid
174180
columns={columns}
175181
onContinue={handleOnContinue}
182+
onBack={onBack}
176183
isLoading={isLoading}
177184
userColumn={(column) => (
178185
<UserTableColumn

src/steps/MatchColumnsStep/components/ColumnGrid.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type ColumnGridProps<T extends string> = {
1111
userColumn: (column: Column<T>) => React.ReactNode
1212
templateColumn: (column: Column<T>) => React.ReactNode
1313
onContinue: (val: Record<string, string>[]) => void
14+
onBack?: () => void
1415
isLoading: boolean
1516
}
1617

@@ -21,6 +22,7 @@ export const ColumnGrid = <T extends string>({
2122
userColumn,
2223
templateColumn,
2324
onContinue,
25+
onBack,
2426
isLoading,
2527
}: ColumnGridProps<T>) => {
2628
const { translations } = useRsi()
@@ -66,7 +68,9 @@ export const ColumnGrid = <T extends string>({
6668
<ContinueButton
6769
isLoading={isLoading}
6870
onContinue={onContinue}
71+
onBack={onBack}
6972
title={translations.matchColumnsStep.nextButtonTitle}
73+
backTitle={translations.matchColumnsStep.backButtonTitle}
7074
/>
7175
</>
7276
)

src/steps/SelectHeaderStep/SelectHeaderStep.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import type { RawData } from "../../types"
99
type SelectHeaderProps = {
1010
data: RawData[]
1111
onContinue: (headerValues: RawData, data: RawData[]) => Promise<void>
12+
onBack?: () => void
1213
}
1314

14-
export const SelectHeaderStep = ({ data, onContinue }: SelectHeaderProps) => {
15+
export const SelectHeaderStep = ({ data, onContinue, onBack }: SelectHeaderProps) => {
1516
const styles = useStyleConfig(
1617
"SelectHeaderStep",
1718
) as (typeof themeOverrides)["components"]["SelectHeaderStep"]["baseStyle"]
@@ -36,7 +37,9 @@ export const SelectHeaderStep = ({ data, onContinue }: SelectHeaderProps) => {
3637
</ModalBody>
3738
<ContinueButton
3839
onContinue={handleContinue}
40+
onBack={onBack}
3941
title={translations.selectHeaderStep.nextButtonTitle}
42+
backTitle={translations.selectHeaderStep.backButtonTitle}
4043
isLoading={isLoading}
4144
/>
4245
</>

src/steps/SelectHeaderStep/tests/SelectHeaderStep.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ describe("Select header step tests", () => {
2828
const selectRowIndex = 2
2929

3030
const onContinue = jest.fn()
31+
const onBack = jest.fn()
3132
render(
3233
<Providers theme={defaultTheme} rsiValues={mockRsiValues}>
3334
<ModalWrapper isOpen={true} onClose={() => {}}>
34-
<SelectHeaderStep data={data} onContinue={onContinue} />
35+
<SelectHeaderStep data={data} onContinue={onContinue} onBack={onBack} />
3536
</ModalWrapper>
3637
</Providers>,
3738
)

src/steps/SelectSheetStep/SelectSheetStep.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import type { themeOverrides } from "../../theme"
77
type SelectSheetProps = {
88
sheetNames: string[]
99
onContinue: (sheetName: string) => Promise<void>
10+
onBack?: () => void
1011
}
1112

12-
export const SelectSheetStep = ({ sheetNames, onContinue }: SelectSheetProps) => {
13+
export const SelectSheetStep = ({ sheetNames, onContinue, onBack }: SelectSheetProps) => {
1314
const [isLoading, setIsLoading] = useState(false)
1415
const { translations } = useRsi()
1516
const [value, setValue] = useState(sheetNames[0])
@@ -42,7 +43,9 @@ export const SelectSheetStep = ({ sheetNames, onContinue }: SelectSheetProps) =>
4243
<ContinueButton
4344
isLoading={isLoading}
4445
onContinue={() => handleOnContinue(value)}
46+
onBack={onBack}
4547
title={translations.uploadStep.selectSheet.nextButtonTitle}
48+
backTitle={translations.uploadStep.selectSheet.backButtonTitle}
4649
/>
4750
</>
4851
)

src/steps/Steps.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,61 @@
1-
import { UploadFlow } from "./UploadFlow"
1+
import { StepState, StepType, UploadFlow } from "./UploadFlow"
22
import { ModalHeader } from "@chakra-ui/react"
33
import { useSteps, Step, Steps as Stepper } from "chakra-ui-steps"
44
import { CgCheck } from "react-icons/cg"
5+
56
import { useRsi } from "../hooks/useRsi"
6-
import { useRsiInitialStep } from "../hooks/useRsiInitialStep"
7+
import { useRef, useState } from "react"
8+
import { steps, stepTypeToStepIndex, stepIndexToStepType } from "../utils/steps"
79

810
const CheckIcon = ({ color }: { color: string }) => <CgCheck size="2.25rem" color={color} />
911

1012
export const Steps = () => {
11-
const { initialStepState, translations } = useRsi()
13+
const { initialStepState, translations, isNavigationEnabled } = useRsi()
1214

13-
const { steps, initialStep } = useRsiInitialStep(initialStepState?.type)
15+
const initialStep = stepTypeToStepIndex(initialStepState?.type)
1416

15-
const { nextStep, activeStep } = useSteps({
17+
const { nextStep, activeStep, setStep } = useSteps({
1618
initialStep,
1719
})
1820

21+
const [state, setState] = useState<StepState>(initialStepState || { type: StepType.upload })
22+
23+
const history = useRef<StepState[]>([])
24+
25+
const onClickStep = (stepIndex: number) => {
26+
const type = stepIndexToStepType(stepIndex)
27+
const historyIdx = history.current.findIndex((v) => v.type === type)
28+
if (historyIdx === -1) return
29+
const nextHistory = history.current.slice(0, historyIdx + 1)
30+
history.current = nextHistory
31+
setState(nextHistory[nextHistory.length - 1])
32+
setStep(stepIndex)
33+
}
34+
35+
const onBack = () => {
36+
onClickStep(Math.max(activeStep - 1, 0))
37+
}
38+
39+
const onNext = (v: StepState) => {
40+
history.current.push(state)
41+
setState(v)
42+
v.type !== StepType.selectSheet && nextStep()
43+
}
44+
1945
return (
2046
<>
2147
<ModalHeader display={["none", "none", "block"]}>
22-
<Stepper activeStep={activeStep} checkIcon={CheckIcon}>
48+
<Stepper
49+
activeStep={activeStep}
50+
checkIcon={CheckIcon}
51+
onClickStep={isNavigationEnabled ? onClickStep : undefined}
52+
>
2353
{steps.map((key) => (
2454
<Step label={translations[key].title} key={key} />
2555
))}
2656
</Stepper>
2757
</ModalHeader>
28-
<UploadFlow nextStep={nextStep} />
58+
<UploadFlow state={state} onNext={onNext} onBack={isNavigationEnabled ? onBack : undefined} />
2959
</>
3060
)
3161
}

src/steps/UploadFlow.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ export type StepState =
4242
}
4343

4444
interface Props {
45-
nextStep: () => void
45+
state: StepState
46+
onNext: (v: StepState) => void
47+
onBack?: () => void
4648
}
4749

48-
export const UploadFlow = ({ nextStep }: Props) => {
50+
export const UploadFlow = ({ state, onNext, onBack }: Props) => {
4951
const {
50-
initialStepState,
5152
maxRecords,
5253
translations,
5354
uploadStepHook,
@@ -57,7 +58,6 @@ export const UploadFlow = ({ nextStep }: Props) => {
5758
rowHook,
5859
tableHook,
5960
} = useRsi()
60-
const [state, setState] = useState<StepState>(initialStepState || { type: StepType.upload })
6161
const [uploadedFile, setUploadedFile] = useState<File | null>(null)
6262
const toast = useToast()
6363
const errorToast = useCallback(
@@ -88,16 +88,15 @@ export const UploadFlow = ({ nextStep }: Props) => {
8888
}
8989
try {
9090
const mappedWorkbook = await uploadStepHook(mapWorkbook(workbook))
91-
setState({
91+
onNext({
9292
type: StepType.selectHeader,
9393
data: mappedWorkbook,
9494
})
95-
nextStep()
9695
} catch (e) {
9796
errorToast((e as Error).message)
9897
}
9998
} else {
100-
setState({ type: StepType.selectSheet, workbook })
99+
onNext({ type: StepType.selectSheet, workbook })
101100
}
102101
}}
103102
/>
@@ -113,15 +112,15 @@ export const UploadFlow = ({ nextStep }: Props) => {
113112
}
114113
try {
115114
const mappedWorkbook = await uploadStepHook(mapWorkbook(state.workbook, sheetName))
116-
setState({
115+
onNext({
117116
type: StepType.selectHeader,
118117
data: mappedWorkbook,
119118
})
120-
nextStep()
121119
} catch (e) {
122120
errorToast((e as Error).message)
123121
}
124122
}}
123+
onBack={onBack}
125124
/>
126125
)
127126
case StepType.selectHeader:
@@ -131,16 +130,16 @@ export const UploadFlow = ({ nextStep }: Props) => {
131130
onContinue={async (...args) => {
132131
try {
133132
const { data, headerValues } = await selectHeaderStepHook(...args)
134-
setState({
133+
onNext({
135134
type: StepType.matchColumns,
136135
data,
137136
headerValues,
138137
})
139-
nextStep()
140138
} catch (e) {
141139
errorToast((e as Error).message)
142140
}
143141
}}
142+
onBack={onBack}
144143
/>
145144
)
146145
case StepType.matchColumns:
@@ -152,19 +151,19 @@ export const UploadFlow = ({ nextStep }: Props) => {
152151
try {
153152
const data = await matchColumnsStepHook(values, rawData, columns)
154153
const dataWithMeta = await addErrorsAndRunHooks(data, fields, rowHook, tableHook)
155-
setState({
154+
onNext({
156155
type: StepType.validateData,
157156
data: dataWithMeta,
158157
})
159-
nextStep()
160158
} catch (e) {
161159
errorToast((e as Error).message)
162160
}
163161
}}
162+
onBack={onBack}
164163
/>
165164
)
166165
case StepType.validateData:
167-
return <ValidationStep initialData={state.data} file={uploadedFile!} />
166+
return <ValidationStep initialData={state.data} file={uploadedFile!} onBack={onBack} />
168167
default:
169168
return <Progress isIndeterminate />
170169
}

0 commit comments

Comments
 (0)