Skip to content

Commit

Permalink
Stepper Component
Browse files Browse the repository at this point in the history
Add Stepper component to library.

Add wrapper component to Stepper

Add empty fragment to Stepper component.

Refactor Stepper component to use Box instead of Wrap.

Add gap between stepper components
  • Loading branch information
hervedombya committed Nov 8, 2023
1 parent 6c588b7 commit 3fad2d9
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
80 changes: 80 additions & 0 deletions src/lib/components/steppers/Stepper.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { createContext, useContext, useState } from 'react';
import {
Add,
ExctractProps,
StepperContextType,
Steps,
Subtract,
} from './types';
import { Steppers } from './Steppers.component';
import { Box } from '../box/Box';

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 }}>
<Box display={'flex'} gap={8}>
<Steppers
activeStep={currentStep}
steps={steps.map((step) => {
return {
title: step.label,
};
})}
/>
<Component {...stepProps} />
</Box>
</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;
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ export { Dropzone } from './components/dropzone/Dropzone';
export { Toast } from './components/toast/Toast.component';
export { ToastProvider, useToast } from './components/toast/ToastProvider';
export { useMutationsHandler } from './components/toast/useMutationsHandler';
export { Stepper } from './components/steppers/Stepper.component';

0 comments on commit 3fad2d9

Please sign in to comment.