diff --git a/src/lib/components/steppers/Stepper.component.tsx b/src/lib/components/steppers/Stepper.component.tsx new file mode 100644 index 0000000000..d6f1b090ee --- /dev/null +++ b/src/lib/components/steppers/Stepper.component.tsx @@ -0,0 +1,77 @@ +import { createContext, useContext, useState } from 'react'; +import { + Add, + ExctractProps, + StepperContextType, + Steps, + Subtract, +} from './types'; +import { Steppers } from './Steppers.component'; + +const StepperContext = createContext(null); + +export const useStepper = < + T extends any[], + StepIndex extends number, + NextIndex = Add, + PrevIndex = Subtract, +>( + index: StepIndex, + steps: readonly [...Steps], +): (NextIndex extends number + ? { + next: (props: ExctractProps) => void; + } + : Record) & + (PrevIndex extends -1 + ? Record + : PrevIndex extends number + ? { + prev: (props: ExctractProps) => void; + } + : Record) => { + const context = useContext(StepperContext); + + if (context === null) { + throw new Error('Cannot use useStepper outside of Stepper'); + } + const { next, prev } = context; + + //@ts-expect-error generic type + return { next, prev }; +}; + +export const Stepper = ({ + steps, +}: { + steps: readonly [...Steps]; +}) => { + const [currentStep, setCurrentStep] = useState(0); + const [stepProps, setStepProps] = useState>({}); + + const next = (props: Record) => { + setCurrentStep(currentStep + 1); + setStepProps(props); + }; + + const prev = (props: Record) => { + setCurrentStep(currentStep - 1); + setStepProps(props); + }; + + const { Component } = steps[currentStep]; + + return ( + + { + return { + title: step.label, + }; + })} + /> + + + ); +}; diff --git a/src/lib/components/steppers/Steppers.component.tsx b/src/lib/components/steppers/Steppers.component.tsx index 12856032da..a537e688d7 100644 --- a/src/lib/components/steppers/Steppers.component.tsx +++ b/src/lib/components/steppers/Steppers.component.tsx @@ -6,8 +6,8 @@ import { Loader } from '../loader/Loader.component'; import { getTheme, getThemePropSelector } from '../../utils'; import { Icon } from '../icon/Icon.component'; type StepProps = { - title: JSX.Element; - content: JSX.Element; + title: React.ReactNode; + content?: React.ReactNode; active?: boolean; completed?: boolean; isLast?: boolean; diff --git a/src/lib/components/steppers/types.ts b/src/lib/components/steppers/types.ts new file mode 100644 index 0000000000..13170ad460 --- /dev/null +++ b/src/lib/components/steppers/types.ts @@ -0,0 +1,48 @@ +import { ReactNode } from 'react'; + +type MAXIMUM_DEPTH = 20; + +type GetResults = T extends Step ? Step : never; + +type Length = T extends { length: infer L } ? L : never; + +type BuildTuple = T extends { + length: L; +} + ? T + : BuildTuple; + +export interface Step { + label: string; + Component: (args: T) => ReactNode; +} + +export interface StepperContextType { + next: (props: Record) => void; + prev: (props: Record) => void; +} + +export declare type Steps< + T extends any[], + Result extends any[] = [], + Depth extends ReadonlyArray = [], +> = Depth['length'] extends MAXIMUM_DEPTH + ? Step[] + : T extends [] + ? [] + : T extends [infer Head] + ? [...Result, GetResults] + : T extends [infer Head, ...infer Tail] + ? Steps<[...Tail], [...Result, GetResults], [...Depth, 1]> + : unknown[] extends T + ? T + : never; + +export type Add = Length< + [...BuildTuple, ...BuildTuple] +>; + +export type Subtract = + BuildTuple extends [...infer U, ...BuildTuple] ? Length : -1; + +export type ExctractProps = T extends Step ? Props : never;