diff --git a/apps/www/content/docs/hooks/use-step.mdx b/apps/www/content/docs/hooks/use-step.mdx new file mode 100644 index 00000000..bed924b4 --- /dev/null +++ b/apps/www/content/docs/hooks/use-step.mdx @@ -0,0 +1,79 @@ +--- +title: useStep +description: Copy text to the clipboard +--- + + +```json doc-gen:file +{ + "file": "./src/components/demos/UseStep/UseStep.tsx", + "codeblock": true +} +``` + + +## Installation + + + + + ### Copy-paste the hook + + Copy and paste the hook code in a `.ts` file. + + ```json doc-gen:file + { + "file": "./src/hooks/use-step.ts", + "codeblock": true + } + ``` + + + + + +### Usage + +```ts +import { useStep } from '~/hooks/use-step'; +``` + +```tsx +const { currentStep, setStep } = useStep(4); + +

{currentStep}

; +``` + +## Details + +### Step Navigation + +- The `useStep` hook provides a straightforward way to manage multi-step navigation in applications, such as wizards or forms. It allows users to move between steps easily while enforcing boundaries based on the maximum step defined. +- The hook ensures that users cannot navigate beyond the defined steps, preventing errors and enhancing user experience. + +### State Management + +- **`currentStep`**: A state variable that holds the current step number, initialized to `1`. This allows components to easily access the current step and render appropriate content based on it. +- **`canGoToNextStep`**: A boolean that indicates whether the user can proceed to the next step. This is useful for enabling or disabling navigation buttons. +- **`canGoToPrevStep`**: A boolean that indicates whether the user can go back to the previous step, providing similar functionality for backward navigation. + +### Step Control Functions + +- **`goToNextStep`**: A function that increments the `currentStep` by `1` if the next step is available. This function can be called directly from UI elements like buttons. +- **`goToPrevStep`**: A function that decrements the `currentStep` by `1` if the previous step exists, allowing users to navigate backward through the steps. +- **`setStep`**: A versatile function that allows setting the current step either directly with a number or by providing a function that computes the new step based on the previous one. This flexibility mimics the API of `useState`. + +### Reset Functionality + +- **`reset`**: A function that resets the `currentStep` back to `1`. This is particularly useful for scenarios where the user might want to restart the process or when the form is submitted successfully. + +### Return Value + +- The hook returns an object containing: +- **`currentStep`**: The current step number, which can be used to render step-specific content. +- **`goToNextStep`**: A function to navigate to the next step. +- **`goToPrevStep`**: A function to navigate to the previous step. +- **`canGoToNextStep`**: A boolean indicating if the next step is available. +- **`canGoToPrevStep`**: A boolean indicating if the previous step is available. +- **`setStep`**: A function to set the current step directly or via a function. +- **`reset`**: A function to reset the step to the initial state. diff --git a/apps/www/src/components/ComponentPreview/Components.tsx b/apps/www/src/components/ComponentPreview/Components.tsx index c2d0e21e..a9b09b0a 100644 --- a/apps/www/src/components/ComponentPreview/Components.tsx +++ b/apps/www/src/components/ComponentPreview/Components.tsx @@ -1213,6 +1213,15 @@ export const Components: Record = { })), ), }, + 'use-step': { + name: 'use-step', + type: 'hook:example', + component: lazy(() => + import('../demos/UseStep').then((module) => ({ + default: module.UseStepDemo, + })), + ), + }, 'use-toggle': { name: 'use-toggle', type: 'hook:example', diff --git a/apps/www/src/components/demos/UseStep/UseStep.tsx b/apps/www/src/components/demos/UseStep/UseStep.tsx new file mode 100644 index 00000000..b2ac0a7d --- /dev/null +++ b/apps/www/src/components/demos/UseStep/UseStep.tsx @@ -0,0 +1,99 @@ +'use client'; + +import { + ChevronLeftIcon, + ChevronRightIcon, + DoubleArrowLeftIcon, + DoubleArrowRightIcon, +} from '@radix-ui/react-icons'; + +import { Button } from '@kosori/ui/button'; + +import { useStep } from '~/hooks/use-step'; + +export const UseStepDemo = () => { + const { + currentStep, + canGoToPrevStep, + canGoToNextStep, + setStep, + goToPrevStep, + goToNextStep, + } = useStep(4); + + return ( +
+

Current step: {currentStep}

+ +
+ + + + + + + + + + + + + + + +
+
+ ); +}; diff --git a/apps/www/src/components/demos/UseStep/index.ts b/apps/www/src/components/demos/UseStep/index.ts new file mode 100644 index 00000000..16d216c2 --- /dev/null +++ b/apps/www/src/components/demos/UseStep/index.ts @@ -0,0 +1 @@ +export * from './UseStep'; diff --git a/apps/www/src/hooks/use-step.ts b/apps/www/src/hooks/use-step.ts new file mode 100644 index 00000000..71101ce2 --- /dev/null +++ b/apps/www/src/hooks/use-step.ts @@ -0,0 +1,71 @@ +import { useCallback, useState } from 'react'; + +/** + * A custom hook that manages multi-step navigation in applications. + * + * @param {number} [maxStep] - The maximum number of steps in the navigation process. + * + * @returns An object containing the following properties: + * - `currentStep`: The current step number, initialized to 1. + * - `goToNextStep`: A function that increments the current step by 1 if the next step is available. + * - `goToPrevStep`: A function that decrements the current step by 1 if the previous step exists. + * - `canGoToNextStep`: A boolean indicating whether the user can proceed to the next step. + * - `canGoToPrevStep`: A boolean indicating whether the user can go back to the previous step. + * - `setStep`: A function that sets the current step either directly with a number or via a function that computes the new step based on the previous one. + * - `reset`: A function that resets the current step back to 1. + * + * @example + * const { currentStep, goToNextStep, setStep, reset } = useStep(5); + * + *

{currentStep}

+ * + * + * + */ +export const useStep = (maxStep: number) => { + const [currentStep, setCurrentStep] = useState(1); + + const canGoToNextStep = currentStep + 1 <= maxStep; + const canGoToPrevStep = currentStep - 1 > 0; + + const setStep = useCallback( + (step: number | ((step: number) => number)) => { + // Allow value to be a function so we have the same API as useState + const newStep = step instanceof Function ? step(currentStep) : step; + + if (newStep >= 1 && newStep <= maxStep) { + setCurrentStep(newStep); + return; + } + + throw new Error('Step not valid'); + }, + [maxStep, currentStep], + ); + + const goToNextStep = useCallback(() => { + if (canGoToNextStep) { + setCurrentStep((step) => step + 1); + } + }, [canGoToNextStep]); + + const goToPrevStep = useCallback(() => { + if (canGoToPrevStep) { + setCurrentStep((step) => step - 1); + } + }, [canGoToPrevStep]); + + const reset = useCallback(() => { + setCurrentStep(1); + }, []); + + return { + currentStep, + goToNextStep, + goToPrevStep, + canGoToNextStep, + canGoToPrevStep, + setStep, + reset, + }; +};