Last active
May 22, 2020 16:28
-
-
Save estrattonbailey/ecc2d190ecac3478f29539b0c33a8913 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Button } from '@components/Button'; | |
import * as Type from '@components/Typography'; | |
import Box from '@components/Box'; | |
import Gutter from '@components/Gutter'; | |
import Container from '@components/Container'; | |
import { useSteps, Step } from '@components/Steps'; | |
enum StepIds { | |
ONE = 'one', | |
TWO = 'two', | |
THREE = 'three', | |
FOUR = 'four', | |
} | |
function StepsDemo() { | |
const [value, valueSet] = React.useState(false); | |
const [activeStepId, activeStepIdSet] = React.useState(StepIds.ONE); | |
const { steps, goTo, goNext, goPrev } = useSteps< | |
StepIds, | |
{ component?: React.ReactNode } | |
>({ | |
steps: [ | |
{ | |
id: StepIds.ONE, | |
next() { | |
return !!value ? StepIds.TWO : StepIds.THREE; | |
}, | |
}, | |
{ | |
id: StepIds.TWO, | |
valid() { | |
return !!value; | |
}, | |
prev() { | |
return StepIds.ONE; | |
}, | |
next() { | |
return StepIds.THREE; | |
}, | |
}, | |
{ | |
id: StepIds.THREE, | |
prev() { | |
return StepIds.TWO; | |
}, | |
next() { | |
return StepIds.FOUR; | |
}, | |
}, | |
{ | |
id: StepIds.FOUR, | |
prev() { | |
return StepIds.THREE; | |
}, | |
}, | |
], | |
activeStepId, | |
activeStepIdSet, | |
animationSpeed: 300, | |
}); | |
return ( | |
<Container size="xs"> | |
<Box mt="sm" display="flex"> | |
{steps.map(step => | |
<Step key={step.id} active={step.active}> | |
<Type.H2>{step.id}</Type.H2> | |
</Step> | |
)} | |
</Box> | |
<Box mt="lg" display="flex" justifyContent="space-between"> | |
{steps.map((step, i) => ( | |
<Button | |
key={step.id} | |
appearance="secondary" | |
size="smallSquare" | |
onClick={() => goTo(step.id)} | |
disabled={!step.valid} | |
> | |
{i + 1} | |
</Button> | |
))} | |
</Box> | |
<Box mt="sm" display="flex" justifyContent="space-between"> | |
<Button appearance="primary" size="small" onClick={() => goPrev()}> | |
Prev | |
</Button> | |
<Button | |
ml="sm" | |
appearance="primary" | |
size="small" | |
onClick={() => { | |
valueSet(true); | |
goNext(); | |
}} | |
> | |
Next | |
</Button> | |
</Box> | |
</Container> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useMemo, useCallback, useLayoutEffect } from 'react'; | |
export type StepProps<Ids, Props> = { | |
id: Ids; | |
next?: () => Ids | undefined; | |
prev?: () => Ids | undefined; | |
valid?: () => boolean; | |
} & Props; | |
export type StepsOptions<Ids, Props> = { | |
steps: StepProps<Ids, Props>[]; | |
activeStepId: Ids; | |
activeStepIdSet(id: Ids): void; | |
animationSpeed?: number; | |
}; | |
export type StepsMethods<Ids> = { | |
goTo(id: Ids): void; | |
goNext(): void; | |
goPrev(): void; | |
}; | |
export type StepsInstance<Ids, Props> = { | |
steps: ({ | |
active: boolean; | |
valid: boolean; | |
next: Ids | undefined; | |
prev: Ids | undefined; | |
} & Omit<StepProps<Ids, Props>, 'next' | 'prev' | 'valid'> & | |
StepsMethods<Ids>)[]; | |
activeStepId: Ids; | |
} & StepsMethods<Ids>; | |
export function useSteps<Ids, Props>({ | |
steps, | |
activeStepId, | |
activeStepIdSet, | |
animationSpeed, | |
}: StepsOptions<Ids, Props>): StepsInstance<Ids, Props> { | |
const [state, stateSet] = useState<{ | |
animating: boolean; | |
queuedUpdate?: string; | |
queuedStep?: Ids; | |
}>({ | |
animating: false, | |
}); | |
// needs to update every time activeStepId changes | |
const activeStep = useMemo( | |
() => steps.filter(({ id }) => id === activeStepId)[0], | |
[steps, activeStepId] | |
); | |
// queued updates, either requests a stepId or 'prev' or 'next' | |
const goTo = useCallback(id => stateSet(s => ({ ...s, queuedStep: id })), [ | |
stateSet, | |
]); | |
const goNext = useCallback(() => { | |
stateSet(s => ({ ...s, queuedUpdate: 'next' })); | |
}, [stateSet]); | |
const goPrev = useCallback(() => { | |
stateSet(s => ({ ...s, queuedUpdate: 'prev' })); | |
}, [stateSet]); | |
// on next tick, respond to queued update | |
useLayoutEffect(() => { | |
const { animating, queuedUpdate, queuedStep } = state; | |
// if in the middle of an animation, or we don't have a valid queued update, return | |
if (animating || !(state.queuedUpdate || state.queuedStep)) return; | |
const { prev, next } = activeStep; | |
// queued stepId from goTo | |
let requestedId = queuedStep; | |
// if no specified step, compute the next/prev step | |
if (!requestedId) { | |
if (queuedUpdate === 'next' && next) { | |
requestedId = next(); | |
} else if (queuedUpdate === 'prev' && prev) { | |
requestedId = prev(); | |
} | |
} | |
if (requestedId && requestedId !== activeStep.id) { | |
const step = steps.filter(s => s.id === requestedId)[0]; | |
// if step exists and is valid | |
if (step && (step.valid ? step.valid() : true)) { | |
// if animation, perform | |
if (animationSpeed) { | |
stateSet(s => ({ | |
...s, | |
animating: true, | |
})); | |
setTimeout(() => { | |
/* order */ | |
activeStepIdSet(step.id); | |
stateSet({ | |
animating: false, | |
queuedStep: undefined, | |
queuedUpdate: undefined, | |
}); | |
/* /order */ | |
}, animationSpeed); | |
} else { | |
//otherwise just update state | |
/* order */ | |
stateSet(s => ({ | |
...s, | |
queuedStep: undefined, | |
queuedUpdate: undefined, | |
})); | |
activeStepIdSet(step.id); | |
/* /order */ | |
} | |
} | |
} | |
}, [activeStep, state, stateSet]); | |
return { | |
steps: useMemo( | |
() => | |
steps.map(({ next, prev, valid, ...step }) => ({ | |
...step, | |
active: step.id === activeStepId && !state.animating, | |
valid: valid ? valid() : true, | |
next: next ? next() : undefined, | |
prev: prev ? prev() : undefined, | |
goTo, | |
goNext, | |
goPrev, | |
})), | |
[activeStepId, steps, state] | |
), | |
activeStepId, | |
goTo, | |
goNext, | |
goPrev, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment