react-wizard-primitive
Advanced tools
Comparing version 1.1.2 to 1.2.0
@@ -0,1 +1,8 @@ | ||
## 1.2.0 (February 10, 2019) | ||
- feature: Added routing support. See README#Routing for details. | ||
## 1.1.2 (February 9, 2019) | ||
- bugfix: wizard step in a nested component now recieves correct wizard step on rerenders | ||
- fixed spelling / grammar in README | ||
## 1.1.1 (February 8, 2019) | ||
@@ -2,0 +9,0 @@ - added live examples in README |
import React, { FunctionComponent } from "react"; | ||
export interface GetStepOptions { | ||
routeTitle?: string; | ||
} | ||
export interface UseWizard { | ||
@@ -10,3 +13,3 @@ activeStepIndex: number; | ||
previousStep: () => void; | ||
getStep: () => Step; | ||
getStep: (options?: GetStepOptions) => Step; | ||
} | ||
@@ -30,3 +33,3 @@ export interface Step { | ||
previousStep: () => void; | ||
getStep: () => Step; | ||
getStep: (options?: GetStepOptions | undefined) => Step; | ||
moveToStep: (stepIndex: number) => void; | ||
@@ -41,4 +44,5 @@ resetToStep: (stepIndex: number) => void; | ||
children: (step: Step) => React.ReactNode | any; | ||
routeTitle?: string; | ||
} | ||
export declare const WizardStep: FunctionComponent<WizardStepProps>; | ||
export default Wizard; |
@@ -1,3 +0,10 @@ | ||
import React, { useState, useContext, useRef } from 'react'; | ||
import React, { useState, useEffect, useContext, useRef } from 'react'; | ||
const getWindow = () => window; | ||
const getRoutingHash = () => getWindow() ? getWindow().location.hash.replace("#", "") : undefined; | ||
const setRoutingHash = (hashValue) => { | ||
if (getWindow()) | ||
getWindow().location.hash = hashValue; | ||
}; | ||
const WizardContext = React.createContext(null); | ||
@@ -7,5 +14,48 @@ const useWizard = () => { | ||
const [maxVisitedStepIndex, setMaxVisitedStepIndex] = useState(0); | ||
const stepTitles = []; | ||
let stepCheckIndex = 0; | ||
useEffect(() => { | ||
const hash = getRoutingHash(); | ||
const newStepIndex = stepTitles.indexOf(hash); | ||
if (newStepIndex >= 0) { | ||
goToStep(newStepIndex); | ||
} | ||
}, []); | ||
useEffect(() => { | ||
const stepsWithTitle = stepTitles.filter(title => !!title); | ||
const allStepTitlesAvailable = stepsWithTitle.length === stepCheckIndex; | ||
const allStepTitlesMissing = stepsWithTitle.length === 0; | ||
if (allStepTitlesAvailable) { | ||
const stepTitle = stepTitles[activeStepIndex]; | ||
setRoutingHash(stepTitle); | ||
return; | ||
} | ||
if (!allStepTitlesMissing) { | ||
const indicesOfMissingTitles = stepTitles | ||
.map((title, index) => (!title ? index : null)) | ||
.filter(title => title !== null); | ||
console.warn(`You have not specified a title for the steps with the indices: ${indicesOfMissingTitles.join(", ")}`); | ||
return; | ||
} | ||
}, [activeStepIndex]); | ||
const getStep = ({ routeTitle } = {}) => { | ||
const stepIndex = stepCheckIndex; | ||
stepTitles.push(routeTitle); | ||
const stepState = { | ||
index: stepIndex, | ||
isActive: activeStepIndex === stepCheckIndex, | ||
hasBeenActive: maxVisitedStepIndex >= stepCheckIndex, | ||
nextStep: () => goToStep(stepIndex + 1), | ||
previousStep: () => goToStep(Math.max(stepIndex - 1, 0)), | ||
resetToStep: () => goToStep(stepIndex, { resetMaxStepIndex: true }), | ||
moveToStep: () => goToStep(stepIndex) | ||
}; | ||
stepCheckIndex++; | ||
return stepState; | ||
}; | ||
const goToStep = (stepIndex, { resetMaxStepIndex = false } = {}) => { | ||
setActiveStepIndex(stepIndex); | ||
setMaxVisitedStepIndex(resetMaxStepIndex ? stepIndex : Math.max(stepIndex, maxVisitedStepIndex)); | ||
if (activeStepIndex !== stepIndex) { | ||
setActiveStepIndex(stepIndex); | ||
setMaxVisitedStepIndex(resetMaxStepIndex ? stepIndex : Math.max(stepIndex, maxVisitedStepIndex)); | ||
} | ||
}; | ||
@@ -24,17 +74,2 @@ const nextStep = () => { | ||
}; | ||
let stepCheckIndex = 0; | ||
const getStep = () => { | ||
const stepIndex = stepCheckIndex; | ||
const stepState = { | ||
index: stepIndex, | ||
isActive: activeStepIndex === stepCheckIndex, | ||
hasBeenActive: maxVisitedStepIndex >= stepCheckIndex, | ||
nextStep: () => goToStep(stepIndex + 1), | ||
previousStep: () => goToStep(Math.max(stepIndex - 1, 0)), | ||
resetToStep: () => goToStep(stepIndex, { resetMaxStepIndex: true }), | ||
moveToStep: () => goToStep(stepIndex) | ||
}; | ||
stepCheckIndex++; | ||
return stepState; | ||
}; | ||
return { | ||
@@ -66,3 +101,5 @@ activeStepIndex, | ||
contextRef.current = wizardContext; | ||
stepRef.current = wizardContext.getStep(); | ||
stepRef.current = wizardContext.getStep({ | ||
routeTitle: props.routeTitle | ||
}); | ||
} | ||
@@ -69,0 +106,0 @@ return props.children(stepRef.current); |
@@ -10,2 +10,9 @@ 'use strict'; | ||
const getWindow = () => window; | ||
const getRoutingHash = () => getWindow() ? getWindow().location.hash.replace("#", "") : undefined; | ||
const setRoutingHash = (hashValue) => { | ||
if (getWindow()) | ||
getWindow().location.hash = hashValue; | ||
}; | ||
const WizardContext = React__default.createContext(null); | ||
@@ -15,5 +22,48 @@ const useWizard = () => { | ||
const [maxVisitedStepIndex, setMaxVisitedStepIndex] = React.useState(0); | ||
const stepTitles = []; | ||
let stepCheckIndex = 0; | ||
React.useEffect(() => { | ||
const hash = getRoutingHash(); | ||
const newStepIndex = stepTitles.indexOf(hash); | ||
if (newStepIndex >= 0) { | ||
goToStep(newStepIndex); | ||
} | ||
}, []); | ||
React.useEffect(() => { | ||
const stepsWithTitle = stepTitles.filter(title => !!title); | ||
const allStepTitlesAvailable = stepsWithTitle.length === stepCheckIndex; | ||
const allStepTitlesMissing = stepsWithTitle.length === 0; | ||
if (allStepTitlesAvailable) { | ||
const stepTitle = stepTitles[activeStepIndex]; | ||
setRoutingHash(stepTitle); | ||
return; | ||
} | ||
if (!allStepTitlesMissing) { | ||
const indicesOfMissingTitles = stepTitles | ||
.map((title, index) => (!title ? index : null)) | ||
.filter(title => title !== null); | ||
console.warn(`You have not specified a title for the steps with the indices: ${indicesOfMissingTitles.join(", ")}`); | ||
return; | ||
} | ||
}, [activeStepIndex]); | ||
const getStep = ({ routeTitle } = {}) => { | ||
const stepIndex = stepCheckIndex; | ||
stepTitles.push(routeTitle); | ||
const stepState = { | ||
index: stepIndex, | ||
isActive: activeStepIndex === stepCheckIndex, | ||
hasBeenActive: maxVisitedStepIndex >= stepCheckIndex, | ||
nextStep: () => goToStep(stepIndex + 1), | ||
previousStep: () => goToStep(Math.max(stepIndex - 1, 0)), | ||
resetToStep: () => goToStep(stepIndex, { resetMaxStepIndex: true }), | ||
moveToStep: () => goToStep(stepIndex) | ||
}; | ||
stepCheckIndex++; | ||
return stepState; | ||
}; | ||
const goToStep = (stepIndex, { resetMaxStepIndex = false } = {}) => { | ||
setActiveStepIndex(stepIndex); | ||
setMaxVisitedStepIndex(resetMaxStepIndex ? stepIndex : Math.max(stepIndex, maxVisitedStepIndex)); | ||
if (activeStepIndex !== stepIndex) { | ||
setActiveStepIndex(stepIndex); | ||
setMaxVisitedStepIndex(resetMaxStepIndex ? stepIndex : Math.max(stepIndex, maxVisitedStepIndex)); | ||
} | ||
}; | ||
@@ -32,17 +82,2 @@ const nextStep = () => { | ||
}; | ||
let stepCheckIndex = 0; | ||
const getStep = () => { | ||
const stepIndex = stepCheckIndex; | ||
const stepState = { | ||
index: stepIndex, | ||
isActive: activeStepIndex === stepCheckIndex, | ||
hasBeenActive: maxVisitedStepIndex >= stepCheckIndex, | ||
nextStep: () => goToStep(stepIndex + 1), | ||
previousStep: () => goToStep(Math.max(stepIndex - 1, 0)), | ||
resetToStep: () => goToStep(stepIndex, { resetMaxStepIndex: true }), | ||
moveToStep: () => goToStep(stepIndex) | ||
}; | ||
stepCheckIndex++; | ||
return stepState; | ||
}; | ||
return { | ||
@@ -74,3 +109,5 @@ activeStepIndex, | ||
contextRef.current = wizardContext; | ||
stepRef.current = wizardContext.getStep(); | ||
stepRef.current = wizardContext.getStep({ | ||
routeTitle: props.routeTitle | ||
}); | ||
} | ||
@@ -77,0 +114,0 @@ return props.children(stepRef.current); |
{ | ||
"name": "react-wizard-primitive", | ||
"version": "1.1.2", | ||
"version": "1.2.0", | ||
"description": "A react wizard primitive without UI restrictions - hooks and render props API available!", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -28,2 +28,3 @@ <h1 align="center"> | ||
- [Step](#step) | ||
- [Routing](#routing) | ||
- [Examples](#examples) | ||
@@ -110,7 +111,10 @@ | ||
> function : Step | ||
> function(options?) : Step | ||
Returns state for the current Step. This needs to be called for each wizard step you want to render. | ||
First call will return informations about the first step, second call about the second, etc. | ||
First call will return information about the first step, second call about the second, etc. | ||
It accepts an _optional_ options object, which takes a string routeTitle in. | ||
See [Routing](#routing) for further information. | ||
### Example | ||
@@ -151,2 +155,4 @@ | ||
It takes an optional string _routeTitle_ as a prop. See [Routing](#routing) for further information. | ||
### Example | ||
@@ -246,2 +252,49 @@ | ||
## Routing | ||
### Basics | ||
Out of the box react-wizard-primitive supports an opt-in routing via hash. | ||
In order to use it, you need to specify a routeTitle in the getStep call or pass it as a prop to the _WizardStep_. | ||
The routeTitle will be used as the hash. | ||
If no routeTitle is provided, react-wizard-primitive won't make any changes to the URL. | ||
If only some steps are provided with a title, we assume that this happened by mistake, and won't change the url either. | ||
Instead we log a warning to the console, indicating which steps are missing a title. | ||
### Initial Hash Route | ||
If a hash is present when the wizard is first rendered, it will try to find a matching step to that hash and jump to it | ||
or otherwise jump to the initial step. | ||
You can use this behaviour to start the wizard at any given point. | ||
### Example | ||
```jsx | ||
<Wizard> | ||
{"yourdomain.com/#/first-step"} | ||
<WizardStep routeTitle="first-step"> | ||
{({ isActive, nextStep }) => | ||
isActive && <div onClick={nextStep}>Step 1</div> | ||
} | ||
</WizardStep> | ||
{"yourdomain.com/#/second-step"} | ||
<WizardStep routeTitle="second-step"> | ||
{({ isActive, nextStep }) => | ||
isActive && <div onClick={nextStep}>Step 2</div> | ||
} | ||
</WizardStep> | ||
{"yourdomain.com/#/third-step"} | ||
<WizardStep routeTitle="third-step"> | ||
{({ isActive, nextStep }) => | ||
isActive && <div onClick={nextStep}>Step 3</div> | ||
} | ||
</WizardStep> | ||
</Wizard> | ||
``` | ||
## Examples | ||
@@ -248,0 +301,0 @@ |
22093
8
262
340