Skip to content

Commit

Permalink
Stepper Component
Browse files Browse the repository at this point in the history
  • Loading branch information
hervedombya committed Nov 8, 2023
1 parent 6c588b7 commit 9bddf0e
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 2 deletions.
77 changes: 77 additions & 0 deletions src/lib/components/steppers/Stepper.component.tsx
Original file line number Diff line number Diff line change
@@ -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<StepperContextType | null>(null);

export const useStepper = <
T extends any[],
StepIndex extends number,
NextIndex = Add<StepIndex, 1>,
PrevIndex = Subtract<StepIndex, 1>,
>(
index: StepIndex,
steps: readonly [...Steps<T>],
): (NextIndex extends number
? {
next: (props: ExctractProps<T[NextIndex]>) => void;
}
: Record<string, unknown>) &
(PrevIndex extends -1
? Record<string, unknown>
: PrevIndex extends number
? {
prev: (props: ExctractProps<T[PrevIndex]>) => void;
}
: Record<string, unknown>) => {
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 = <T extends any[]>({
steps,
}: {
steps: readonly [...Steps<T>];
}) => {
const [currentStep, setCurrentStep] = useState<number>(0);
const [stepProps, setStepProps] = useState<Record<string, unknown>>({});

const next = (props: Record<string, unknown>) => {
setCurrentStep(currentStep + 1);
setStepProps(props);
};

const prev = (props: Record<string, unknown>) => {
setCurrentStep(currentStep - 1);
setStepProps(props);
};

const { Component } = steps[currentStep];

return (
<StepperContext.Provider value={{ next, prev }}>
<Steppers
activeStep={currentStep}
steps={steps.map((step) => {
return {
title: step.label,
};
})}
/>
<Component {...stepProps} />
</StepperContext.Provider>
);
};
4 changes: 2 additions & 2 deletions src/lib/components/steppers/Steppers.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
48 changes: 48 additions & 0 deletions src/lib/components/steppers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ReactNode } from 'react';

type MAXIMUM_DEPTH = 20;

type GetResults<T> = T extends Step<infer Props> ? Step<Props> : never;

type Length<T extends any[]> = T extends { length: infer L } ? L : never;

type BuildTuple<L extends number, T extends any[] = []> = T extends {
length: L;
}
? T
: BuildTuple<L, [...T, any]>;

export interface Step<T> {
label: string;
Component: (args: T) => ReactNode;
}

export interface StepperContextType {
next: (props: Record<string, unknown>) => void;
prev: (props: Record<string, unknown>) => void;
}

export declare type Steps<
T extends any[],
Result extends any[] = [],
Depth extends ReadonlyArray<number> = [],
> = Depth['length'] extends MAXIMUM_DEPTH
? Step<unknown>[]
: T extends []
? []
: T extends [infer Head]
? [...Result, GetResults<Head>]
: T extends [infer Head, ...infer Tail]
? Steps<[...Tail], [...Result, GetResults<Head>], [...Depth, 1]>
: unknown[] extends T
? T
: never;

export type Add<A extends number, B extends number> = Length<
[...BuildTuple<A>, ...BuildTuple<B>]
>;

export type Subtract<A extends number, B extends number> =
BuildTuple<A> extends [...infer U, ...BuildTuple<B>] ? Length<U> : -1;

export type ExctractProps<T> = T extends Step<infer Props> ? Props : never;

0 comments on commit 9bddf0e

Please sign in to comment.