From 8d9d86ea74174009978ff3417e1d08023b4fd95c Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 8 May 2023 13:09:38 -0300 Subject: [PATCH 01/62] feat(stepper): add component with docs --- apps/www/components/examples/index.tsx | 18 + .../examples/stepper/clickable-steps.tsx | 56 +++ .../examples/stepper/custom-icons.tsx | 57 +++ .../stepper/custom-success-error-icon.tsx | 61 +++ apps/www/components/examples/stepper/demo.tsx | 55 +++ .../examples/stepper/descriptions.tsx | 55 +++ .../examples/stepper/label-orientation.tsx | 55 +++ .../examples/stepper/optional-steps.tsx | 55 +++ .../examples/stepper/orientation.tsx | 55 +++ .../components/examples/stepper/states.tsx | 76 ++++ apps/www/components/ui/stepper.tsx | 421 ++++++++++++++++++ apps/www/components/ui/use-steps.ts | 121 +++++ apps/www/config/components.ts | 5 + apps/www/config/docs.ts | 6 + apps/www/content/docs/components/stepper.mdx | 247 ++++++++++ apps/www/pages/api/components.json | 10 + 16 files changed, 1353 insertions(+) create mode 100644 apps/www/components/examples/stepper/clickable-steps.tsx create mode 100644 apps/www/components/examples/stepper/custom-icons.tsx create mode 100644 apps/www/components/examples/stepper/custom-success-error-icon.tsx create mode 100644 apps/www/components/examples/stepper/demo.tsx create mode 100644 apps/www/components/examples/stepper/descriptions.tsx create mode 100644 apps/www/components/examples/stepper/label-orientation.tsx create mode 100644 apps/www/components/examples/stepper/optional-steps.tsx create mode 100644 apps/www/components/examples/stepper/orientation.tsx create mode 100644 apps/www/components/examples/stepper/states.tsx create mode 100644 apps/www/components/ui/stepper.tsx create mode 100644 apps/www/components/ui/use-steps.ts create mode 100644 apps/www/content/docs/components/stepper.mdx diff --git a/apps/www/components/examples/index.tsx b/apps/www/components/examples/index.tsx index c68a24f75be..f98a61a31c9 100644 --- a/apps/www/components/examples/index.tsx +++ b/apps/www/components/examples/index.tsx @@ -58,6 +58,15 @@ import { SheetPosition } from "@/components/examples/sheet/position" import { SheetSize } from "@/components/examples/sheet/size" import { SkeletonDemo } from "@/components/examples/skeleton/demo" import { SliderDemo } from "@/components/examples/slider/demo" +import { StepperWithClickableSteps } from "@/components/examples/stepper/clickable-steps" +import { StepperWithCustomIcons } from "@/components/examples/stepper/custom-icons" +import { StepperWithCustomSuccessErrorIcon } from "@/components/examples/stepper/custom-success-error-icon" +import { StepperDemo } from "@/components/examples/stepper/demo" +import { StepperWithDescriptions } from "@/components/examples/stepper/descriptions" +import { StepperWithLabelOrientation } from "@/components/examples/stepper/label-orientation" +import { StepperWithOptionalSteps } from "@/components/examples/stepper/optional-steps" +import { StepperVertical } from "@/components/examples/stepper/orientation" +import { StepperWithStates } from "@/components/examples/stepper/states" import { SwitchDemo } from "@/components/examples/switch/demo" import { TabsDemo } from "@/components/examples/tabs/demo" import { TextareaDemo } from "@/components/examples/textarea/demo" @@ -153,6 +162,15 @@ export const examples = { SheetPosition, SkeletonDemo, SliderDemo, + StepperDemo, + StepperVertical, + StepperWithClickableSteps, + StepperWithCustomIcons, + StepperWithCustomSuccessErrorIcon, + StepperWithDescriptions, + StepperWithLabelOrientation, + StepperWithOptionalSteps, + StepperWithStates, SwitchDemo, TabsDemo, TextareaDemo, diff --git a/apps/www/components/examples/stepper/clickable-steps.tsx b/apps/www/components/examples/stepper/clickable-steps.tsx new file mode 100644 index 00000000000..928d60c3d96 --- /dev/null +++ b/apps/www/components/examples/stepper/clickable-steps.tsx @@ -0,0 +1,56 @@ +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperWithClickableSteps = () => { + const { + nextStep, + prevStep, + setStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ setStep(step)} activeStep={activeStep}> + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/custom-icons.tsx b/apps/www/components/examples/stepper/custom-icons.tsx new file mode 100644 index 00000000000..7c7295ca46a --- /dev/null +++ b/apps/www/components/examples/stepper/custom-icons.tsx @@ -0,0 +1,57 @@ +import { Calendar, Twitter, User } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1", icon: }, + { label: "Step 2", icon: }, + { label: "Step 3", icon: }, +] satisfies StepConfig[] + +export const StepperWithCustomIcons = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/custom-success-error-icon.tsx b/apps/www/components/examples/stepper/custom-success-error-icon.tsx new file mode 100644 index 00000000000..1ec247deb7f --- /dev/null +++ b/apps/www/components/examples/stepper/custom-success-error-icon.tsx @@ -0,0 +1,61 @@ +import { CheckCircle2, XCircle } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperWithCustomSuccessErrorIcon = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ } + errorIcon={} + > + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/demo.tsx b/apps/www/components/examples/stepper/demo.tsx new file mode 100644 index 00000000000..877931351b2 --- /dev/null +++ b/apps/www/components/examples/stepper/demo.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperDemo = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/descriptions.tsx b/apps/www/components/examples/stepper/descriptions.tsx new file mode 100644 index 00000000000..536c2dc5cb4 --- /dev/null +++ b/apps/www/components/examples/stepper/descriptions.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1", description: "Frist description" }, + { label: "Step 2", description: "Second description" }, + { label: "Step 3", description: "Third description" }, +] satisfies StepConfig[] + +export const StepperWithDescriptions = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/label-orientation.tsx b/apps/www/components/examples/stepper/label-orientation.tsx new file mode 100644 index 00000000000..c475fe48f48 --- /dev/null +++ b/apps/www/components/examples/stepper/label-orientation.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1", description: "Frist description" }, + { label: "Step 2", description: "Second description" }, + { label: "Step 3", description: "Third description" }, +] satisfies StepConfig[] + +export const StepperWithLabelOrientation = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/optional-steps.tsx b/apps/www/components/examples/stepper/optional-steps.tsx new file mode 100644 index 00000000000..0cd911995dc --- /dev/null +++ b/apps/www/components/examples/stepper/optional-steps.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2", optional: true, optionalLabel: "Optional" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperWithOptionalSteps = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/orientation.tsx b/apps/www/components/examples/stepper/orientation.tsx new file mode 100644 index 00000000000..a3061817c8d --- /dev/null +++ b/apps/www/components/examples/stepper/orientation.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/components/ui/button" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperVertical = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/examples/stepper/states.tsx b/apps/www/components/examples/stepper/states.tsx new file mode 100644 index 00000000000..dba2b0206c5 --- /dev/null +++ b/apps/www/components/examples/stepper/states.tsx @@ -0,0 +1,76 @@ +import { useState } from "react" + +import { Button } from "@/components/ui/button" +import { Label } from "@/components/ui/label" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +import { Step, StepConfig, Steps } from "@/components/ui/stepper" +import { useSteps } from "@/components/ui/use-steps" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperWithStates = () => { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + const [value, setValue] = useState<"loading" | "error">("loading") + + return ( +
+ setValue(value as "loading" | "error")} + className="mb-4" + > +
+ + +
+
+ + +
+
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/components/ui/stepper.tsx b/apps/www/components/ui/stepper.tsx new file mode 100644 index 00000000000..559449f373c --- /dev/null +++ b/apps/www/components/ui/stepper.tsx @@ -0,0 +1,421 @@ +import * as React from "react" +import { VariantProps, cva } from "class-variance-authority" +import { Check, Loader2, X } from "lucide-react" + +import { cn } from "@/lib/utils" + +import { Button } from "./button" +import { Separator } from "./separator" +import { useMediaQuery } from "./use-steps" + +/********** Context **********/ + +interface StepsContextValue extends StepsProps { + isClickable?: boolean + isError?: boolean + isLoading?: boolean + isVertical?: boolean + isLabelVertical?: boolean + stepCount?: number +} + +const StepsContext = React.createContext({ + activeStep: 0, +}) + +export const useStepsContext = () => React.useContext(StepsContext) + +export const StepsProvider: React.FC<{ + value: StepsContextValue + children: React.ReactNode +}> = ({ value, children }) => { + const isError = value.state === "error" + const isLoading = value.state === "loading" + + const isVertical = value.orientation === "vertical" + const isLabelVertical = + value.orientation !== "vertical" && value.labelOrientation === "vertical" + + return ( + + {children} + + ) +} + +/********** Steps **********/ + +export interface StepsProps { + activeStep: number + orientation?: "vertical" | "horizontal" + state?: "loading" | "error" + responsive?: boolean + onClickStep?: (step: number) => void + successIcon?: React.ReactElement + errorIcon?: React.ReactElement + labelOrientation?: "vertical" | "horizontal" + children?: React.ReactNode + variant?: "default" | "ghost" | "outline" | "secondary" +} + +export const Steps = React.forwardRef( + ( + { + activeStep = 0, + state, + responsive = true, + orientation: orientationProp = "horizontal", + onClickStep, + labelOrientation = "horizontal", + children, + errorIcon, + successIcon, + variant = "default", + }, + ref + ) => { + const childArr = React.Children.toArray(children) + + const stepCount = childArr.length + + const renderHorizontalContent = () => { + if (activeStep <= childArr.length) { + return React.Children.map(childArr[activeStep], (node) => { + if (!React.isValidElement(node)) return + return React.Children.map( + node.props.children, + (childNode) => childNode + ) + }) + } + return null + } + + const isClickable = !!onClickStep + + const isMobile = useMediaQuery("(max-width: 43em)") + + const orientation = isMobile && responsive ? "vertical" : orientationProp + + return ( + +
+ {React.Children.map(children, (child, i) => { + const isCompletedStep = + (React.isValidElement(child) && child.props.isCompletedStep) ?? + i < activeStep + const isLastStep = i === stepCount - 1 + const isCurrentStep = i === activeStep + + const stepProps = { + index: i, + isCompletedStep, + isCurrentStep, + isLastStep, + } + + if (React.isValidElement(child)) { + return React.cloneElement(child, stepProps) + } + + return null + })} +
+ {orientation === "horizontal" && renderHorizontalContent()} +
+ ) + } +) + +Steps.displayName = "Steps" + +/********** Step **********/ + +const stepVariants = cva("flex-row flex relative gap-2", { + variants: { + isLastStep: { + true: "flex-[0_0_auto] justify-end", + false: "flex-[1_0_auto] justify-start", + }, + isVertical: { + true: "flex-col", + false: "items-center", + }, + isClickable: { + true: "cursor-pointer", + }, + }, + compoundVariants: [ + { + isVertical: true, + isLastStep: true, + class: "flex-col items-start flex-[1_0_auto] w-full justify-start", + }, + ], +}) + +export interface StepConfig extends StepLabelProps { + icon?: React.ReactElement +} + +export interface StepProps + extends React.HTMLAttributes, + VariantProps, + StepConfig { + isCompletedStep?: boolean +} + +interface StepStatus { + index: number + isCompletedStep?: boolean + isCurrentStep?: boolean +} + +interface StepAndStatusProps extends StepProps, StepStatus {} + +export const Step = React.forwardRef( + (props, ref) => { + const { + children, + description, + icon: CustomIcon, + index, + isCompletedStep, + isCurrentStep, + isLastStep, + label, + optional, + optionalLabel, + } = props + + const { + isVertical, + isError, + isLoading, + successIcon: CustomSuccessIcon, + errorIcon: CustomErrorIcon, + isLabelVertical, + onClickStep, + isClickable, + variant, + } = useStepsContext() + + const hasVisited = isCurrentStep || isCompletedStep + + const handleClick = (index: number) => { + if (isClickable && onClickStep) { + onClickStep(index) + } + } + + const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon]) + + const Success = React.useMemo( + () => CustomSuccessIcon ?? , + [CustomSuccessIcon] + ) + + const Error = React.useMemo( + () => CustomErrorIcon ?? , + [CustomErrorIcon] + ) + + const RenderIcon = React.useMemo(() => { + if (isCompletedStep) return Success + if (isCurrentStep) { + if (isError) return Error + if (isLoading) return + } + if (Icon) return Icon + return (index || 0) + 1 + }, [ + isCompletedStep, + Success, + isCurrentStep, + Icon, + index, + isError, + Error, + isLoading, + ]) + + return ( +
handleClick(index)} + aria-disabled={!hasVisited} + > +
+ + +
+ + {(isCurrentStep || isCompletedStep) && children} + +
+ ) + } +) + +Step.displayName = "Step" + +/********** StepLabel **********/ + +interface StepLabelProps { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + optionalLabel?: string | React.ReactNode +} + +const StepLabel = ({ + isCurrentStep, + label, + description, + optional, + optionalLabel, +}: StepLabelProps & { + isCurrentStep?: boolean +}) => { + const { isLabelVertical } = useStepsContext() + + const shouldRender = !!label || !!description + + const renderOptionalLabel = !!optional && !!optionalLabel + + return shouldRender ? ( +
+ {!!label && ( +

+ {label} + {renderOptionalLabel && ( + + ({optionalLabel}) + + )} +

+ )} + {!!description && ( +

{description}

+ )} +
+ ) : null +} + +StepLabel.displayName = "StepLabel" + +/********** Connector **********/ + +interface ConnectorProps extends React.HTMLAttributes { + isCompletedStep: boolean + isLastStep?: boolean | null + hasLabel?: boolean + index: number +} + +const Connector = React.memo( + ({ isCompletedStep, children, isLastStep }: ConnectorProps) => { + const { isVertical } = useStepsContext() + + if (isVertical) { + return ( +
+ {!isCompletedStep && ( +
{children}
+ )} +
+ ) + } + + if (isLastStep) { + return null + } + + return ( + + ) + } +) + +Connector.displayName = "Connector" diff --git a/apps/www/components/ui/use-steps.ts b/apps/www/components/ui/use-steps.ts new file mode 100644 index 00000000000..11cf7e1610e --- /dev/null +++ b/apps/www/components/ui/use-steps.ts @@ -0,0 +1,121 @@ +import * as React from "react" + +import { StepProps } from "./stepper" + +type UseSteps = { + initialStep: number + steps: Pick< + StepProps, + "label" | "description" | "optional" | "optionalLabel" | "icon" + >[] +} + +type UseStepsReturn = { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number + isDisabledStep: boolean + isLastStep: boolean + isOptionalStep: boolean | undefined +} + +export function useSteps({ initialStep, steps }: UseSteps): UseStepsReturn { + const [activeStep, setActiveStep] = React.useState(initialStep) + + const nextStep = () => { + setActiveStep((prev) => prev + 1) + } + + const prevStep = () => { + setActiveStep((prev) => prev - 1) + } + + const resetSteps = () => { + setActiveStep(initialStep) + } + + const setStep = (step: number) => { + setActiveStep(step) + } + + const isDisabledStep = activeStep === 0 + + const isLastStep = activeStep === steps.length - 1 + + const isOptionalStep = steps[activeStep]?.optional + + return { + nextStep, + prevStep, + resetSteps, + setStep, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } +} + +interface UseMediaQueryOptions { + getInitialValueInEffect: boolean +} + +type MediaQueryCallback = (event: { matches: boolean; media: string }) => void + +/** + * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia + * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent + * */ +function attachMediaListener( + query: MediaQueryList, + callback: MediaQueryCallback +) { + try { + query.addEventListener("change", callback) + return () => query.removeEventListener("change", callback) + } catch (e) { + query.addListener(callback) + return () => query.removeListener(callback) + } +} + +function getInitialValue(query: string, initialValue?: boolean) { + if (typeof initialValue === "boolean") { + return initialValue + } + + if (typeof window !== "undefined" && "matchMedia" in window) { + return window.matchMedia(query).matches + } + + return false +} + +export function useMediaQuery( + query: string, + initialValue?: boolean, + { getInitialValueInEffect }: UseMediaQueryOptions = { + getInitialValueInEffect: true, + } +) { + const [matches, setMatches] = React.useState( + getInitialValueInEffect ? false : getInitialValue(query, initialValue) + ) + const queryRef = React.useRef() + + React.useEffect(() => { + if ("matchMedia" in window) { + queryRef.current = window.matchMedia(query) + setMatches(queryRef.current.matches) + return attachMediaListener(queryRef.current, (event) => + setMatches(event.matches) + ) + } + + return undefined + }, [query]) + + return matches +} diff --git a/apps/www/config/components.ts b/apps/www/config/components.ts index 70c66c0ef9a..fe3fbf899c8 100644 --- a/apps/www/config/components.ts +++ b/apps/www/config/components.ts @@ -167,6 +167,11 @@ export const components = [ dependencies: ["@radix-ui/react-slider"], files: ["components/ui/slider.tsx"], }, + { + component: "stepper", + name: "Stepper", + files: ["components/ui/stepper.tsx", "components/ui/use-steps.ts"], + }, { component: "switch", name: "Switch", diff --git a/apps/www/config/docs.ts b/apps/www/config/docs.ts index 5a9694f3cc2..02ea7b4783b 100644 --- a/apps/www/config/docs.ts +++ b/apps/www/config/docs.ts @@ -238,6 +238,12 @@ export const docsConfig: DocsConfig = { href: "/docs/components/slider", items: [], }, + { + title: "Stepper", + href: "/docs/components/stepper", + label: "New", + items: [], + }, { title: "Switch", href: "/docs/components/switch", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx new file mode 100644 index 00000000000..9209ed62e14 --- /dev/null +++ b/apps/www/content/docs/components/stepper.mdx @@ -0,0 +1,247 @@ +--- +title: Stepper +description: Display content divided into a steps sequence. +component: true +--- + + + + + +## Installation + +```bash +npx shadcn-ui add stepper +``` + + + + Manual Installation + + +1. Copy and paste the following code into your project. + + + +4. Copy and paste the `useSteps` hook into your project. + + + + + + + + +## Usage + +```tsx +import { Step, Steps, useSteps } from "@/components/ui/stepper" +``` + +```tsx {1,4-16} +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperDemo = () => { + const { + nextStep, + prevStep, + resetSteps, + setStep, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useSteps({ + initialStep: 0, + steps, + }) + + return ( + <> + + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+ + ) +} +``` + +## API + +### `` + +```tsx +interface StepsProps { + activeStep: number + orientation?: "vertical" | "horizontal" + state?: "loading" | "error" + responsive?: boolean + onClickStep?: (step: number) => void + successIcon?: React.ReactElement + errorIcon?: React.ReactElement + labelOrientation?: "vertical" | "horizontal" + children?: React.ReactNode + variant?: "default" | "ghost" | "outline" | "secondary" +} +``` + +### `` + +```tsx +interface StepLabelProps { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + optionalLabel?: string | React.ReactNode +} + +interface StepConfig extends StepLabelProps { + icon?: React.ReactElement +} + +interface StepProps + extends React.HTMLAttributes, + VariantProps, + StepConfig { + isCompletedStep?: boolean +} + +interface StepStatus { + index: number + isCompletedStep?: boolean + isCurrentStep?: boolean +} + +interface StepAndStatusProps extends StepProps, StepStatus {} +``` + +### `useSteps` + +`useSteps` returns values and functions that allow to render the stepper in a modular way. + +```tsx +function useSteps( + initialStep: number, + steps: { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + optionalLabel?: string | React.ReactNode + icon?: React.ReactElement + }[] +): { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number + isDisabledStep: boolean + isLastStep: boolean + isOptionalStep: boolean | undefined +} +``` + +## Examples + +### Default + + + + + +--- + +### Orientation + +We can pass the `orientation` prop to the Steps component to set the orientation as "vertical" or "horizontal". + + + + + +### Descriptions + +The Step component also accepts a `description` prop which can be used to provide some extra information about the step. + + + + + +### Custom Icons + +If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component. + + + + + +### Custom Success/Error icon + +If you want to show a custom success/error icon instead of the default, you can do so by using the `successIcon` and `errorIcon` prop on the Steps component. + + + + + +### Label orientation + +If you would like the labels to be positioned below the step icons you can do so using the `labelOrientation` prop on the Steps component. + + + + + +### States + +Sometimes it is useful to show visual feedback to the user depending on some asynchronous logic. In this case we can use the `state` prop. + + + + + +### Clickable Steps + +By providing the onClickStep prop the steps will become clickable. + + + + + +### Optional Steps + +If you want to make a step optional, you can use the value of optional and optionalLabel in the array of steps. + + + + + +### Responsive + +By default, the stepper behaves vertically on small screens. If you need to remove that functionality, you can change the `responsive` prop to false in the Steps component. diff --git a/apps/www/pages/api/components.json b/apps/www/pages/api/components.json index af23326aece..e9efec2a2a3 100644 --- a/apps/www/pages/api/components.json +++ b/apps/www/pages/api/components.json @@ -387,6 +387,16 @@ "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SliderPrimitive from \"@radix-ui/react-slider\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Slider = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n \n \n \n \n \n))\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider }\n" } ] + }, + { + "component": "stepper", + "name": "Stepper", + "files": [ + { + "name": "stepper.tsx", + "dir": "components/ui" + } + ] }, { "component": "switch", From 3820faab977a4bb4ce6be9df145cee2f13d02d9e Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 10 May 2023 10:46:47 -0300 Subject: [PATCH 02/62] refactor(stepper): update component names --- .../examples/stepper/clickable-steps.tsx | 16 +- .../examples/stepper/custom-icons.tsx | 16 +- .../stepper/custom-success-error-icon.tsx | 16 +- apps/www/components/examples/stepper/demo.tsx | 16 +- .../examples/stepper/descriptions.tsx | 16 +- .../examples/stepper/label-orientation.tsx | 16 +- .../examples/stepper/optional-steps.tsx | 16 +- .../examples/stepper/orientation.tsx | 16 +- .../components/examples/stepper/states.tsx | 16 +- apps/www/components/ui/stepper.tsx | 279 +++++++++--------- .../ui/{use-steps.ts => use-stepper.ts} | 13 +- apps/www/config/components.ts | 2 +- apps/www/content/docs/components/stepper.mdx | 49 +-- 13 files changed, 248 insertions(+), 239 deletions(-) rename apps/www/components/ui/{use-steps.ts => use-stepper.ts} (92%) diff --git a/apps/www/components/examples/stepper/clickable-steps.tsx b/apps/www/components/examples/stepper/clickable-steps.tsx index 928d60c3d96..6eadb30b8e2 100644 --- a/apps/www/components/examples/stepper/clickable-steps.tsx +++ b/apps/www/components/examples/stepper/clickable-steps.tsx @@ -1,12 +1,12 @@ import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperWithClickableSteps = () => { const { @@ -18,22 +18,22 @@ export const StepperWithClickableSteps = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- setStep(step)} activeStep={activeStep}> + setStep(step)} activeStep={activeStep}> {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/custom-icons.tsx b/apps/www/components/examples/stepper/custom-icons.tsx index 7c7295ca46a..70ea1018fb2 100644 --- a/apps/www/components/examples/stepper/custom-icons.tsx +++ b/apps/www/components/examples/stepper/custom-icons.tsx @@ -1,14 +1,14 @@ import { Calendar, Twitter, User } from "lucide-react" import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1", icon: }, { label: "Step 2", icon: }, { label: "Step 3", icon: }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperWithCustomIcons = () => { const { @@ -19,22 +19,22 @@ export const StepperWithCustomIcons = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/custom-success-error-icon.tsx b/apps/www/components/examples/stepper/custom-success-error-icon.tsx index 1ec247deb7f..cdb589aa9fa 100644 --- a/apps/www/components/examples/stepper/custom-success-error-icon.tsx +++ b/apps/www/components/examples/stepper/custom-success-error-icon.tsx @@ -1,14 +1,14 @@ import { CheckCircle2, XCircle } from "lucide-react" import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperWithCustomSuccessErrorIcon = () => { const { @@ -19,26 +19,26 @@ export const StepperWithCustomSuccessErrorIcon = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- } errorIcon={} > {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/demo.tsx b/apps/www/components/examples/stepper/demo.tsx index 877931351b2..8f899d67170 100644 --- a/apps/www/components/examples/stepper/demo.tsx +++ b/apps/www/components/examples/stepper/demo.tsx @@ -1,12 +1,12 @@ import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperDemo = () => { const { @@ -17,22 +17,22 @@ export const StepperDemo = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/descriptions.tsx b/apps/www/components/examples/stepper/descriptions.tsx index 536c2dc5cb4..a5b77a80e36 100644 --- a/apps/www/components/examples/stepper/descriptions.tsx +++ b/apps/www/components/examples/stepper/descriptions.tsx @@ -1,12 +1,12 @@ import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1", description: "Frist description" }, { label: "Step 2", description: "Second description" }, { label: "Step 3", description: "Third description" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperWithDescriptions = () => { const { @@ -17,22 +17,22 @@ export const StepperWithDescriptions = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/label-orientation.tsx b/apps/www/components/examples/stepper/label-orientation.tsx index c475fe48f48..739600fc187 100644 --- a/apps/www/components/examples/stepper/label-orientation.tsx +++ b/apps/www/components/examples/stepper/label-orientation.tsx @@ -1,12 +1,12 @@ import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1", description: "Frist description" }, { label: "Step 2", description: "Second description" }, { label: "Step 3", description: "Third description" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperWithLabelOrientation = () => { const { @@ -17,22 +17,22 @@ export const StepperWithLabelOrientation = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/optional-steps.tsx b/apps/www/components/examples/stepper/optional-steps.tsx index 0cd911995dc..9740476ac1c 100644 --- a/apps/www/components/examples/stepper/optional-steps.tsx +++ b/apps/www/components/examples/stepper/optional-steps.tsx @@ -1,12 +1,12 @@ import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2", optional: true, optionalLabel: "Optional" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperWithOptionalSteps = () => { const { @@ -17,22 +17,22 @@ export const StepperWithOptionalSteps = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/orientation.tsx b/apps/www/components/examples/stepper/orientation.tsx index a3061817c8d..715a78e58b4 100644 --- a/apps/www/components/examples/stepper/orientation.tsx +++ b/apps/www/components/examples/stepper/orientation.tsx @@ -1,12 +1,12 @@ import { Button } from "@/components/ui/button" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperVertical = () => { const { @@ -17,22 +17,22 @@ export const StepperVertical = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/examples/stepper/states.tsx b/apps/www/components/examples/stepper/states.tsx index dba2b0206c5..94402830148 100644 --- a/apps/www/components/examples/stepper/states.tsx +++ b/apps/www/components/examples/stepper/states.tsx @@ -3,14 +3,14 @@ import { useState } from "react" import { Button } from "@/components/ui/button" import { Label } from "@/components/ui/label" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" -import { Step, StepConfig, Steps } from "@/components/ui/stepper" -import { useSteps } from "@/components/ui/use-steps" +import { Stepper, StepperConfig, StepperStep } from "@/components/ui/stepper" +import { useStepper } from "@/components/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperWithStates = () => { const { @@ -21,7 +21,7 @@ export const StepperWithStates = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) @@ -45,15 +45,15 @@ export const StepperWithStates = () => {
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/components/ui/stepper.tsx b/apps/www/components/ui/stepper.tsx index 559449f373c..97ba2d59f2d 100644 --- a/apps/www/components/ui/stepper.tsx +++ b/apps/www/components/ui/stepper.tsx @@ -6,11 +6,11 @@ import { cn } from "@/lib/utils" import { Button } from "./button" import { Separator } from "./separator" -import { useMediaQuery } from "./use-steps" +import { useMediaQuery } from "./use-stepper" -/********** Context **********/ +/********** StepperProvider **********/ -interface StepsContextValue extends StepsProps { +interface StepperContextValue extends StepperProps { isClickable?: boolean isError?: boolean isLoading?: boolean @@ -19,14 +19,14 @@ interface StepsContextValue extends StepsProps { stepCount?: number } -const StepsContext = React.createContext({ +const StepsContext = React.createContext({ activeStep: 0, }) -export const useStepsContext = () => React.useContext(StepsContext) +export const useStepperContext = () => React.useContext(StepsContext) -export const StepsProvider: React.FC<{ - value: StepsContextValue +export const StepperProvider: React.FC<{ + value: StepperContextValue children: React.ReactNode }> = ({ value, children }) => { const isError = value.state === "error" @@ -51,9 +51,9 @@ export const StepsProvider: React.FC<{ ) } -/********** Steps **********/ +/********** Stepper **********/ -export interface StepsProps { +export interface StepperProps { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -66,7 +66,7 @@ export interface StepsProps { variant?: "default" | "ghost" | "outline" | "secondary" } -export const Steps = React.forwardRef( +export const Stepper = React.forwardRef( ( { activeStep = 0, @@ -106,7 +106,7 @@ export const Steps = React.forwardRef( const orientation = isMobile && responsive ? "vertical" : orientationProp return ( - ( })}
{orientation === "horizontal" && renderHorizontalContent()} - + ) } ) -Steps.displayName = "Steps" +Stepper.displayName = "Stepper" -/********** Step **********/ +/********** StepperStep **********/ const stepVariants = cva("flex-row flex relative gap-2", { variants: { @@ -182,166 +182,164 @@ const stepVariants = cva("flex-row flex relative gap-2", { ], }) -export interface StepConfig extends StepLabelProps { +export interface StepperConfig extends StepperStepLabelProps { icon?: React.ReactElement } -export interface StepProps +export interface StepperStepProps extends React.HTMLAttributes, VariantProps, - StepConfig { + StepperConfig { isCompletedStep?: boolean } -interface StepStatus { +interface StepperStepStatus { index: number isCompletedStep?: boolean isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus {} - -export const Step = React.forwardRef( - (props, ref) => { - const { - children, - description, - icon: CustomIcon, - index, - isCompletedStep, - isCurrentStep, - isLastStep, - label, - optional, - optionalLabel, - } = props - - const { - isVertical, - isError, - isLoading, - successIcon: CustomSuccessIcon, - errorIcon: CustomErrorIcon, - isLabelVertical, - onClickStep, - isClickable, - variant, - } = useStepsContext() - - const hasVisited = isCurrentStep || isCompletedStep - - const handleClick = (index: number) => { - if (isClickable && onClickStep) { - onClickStep(index) - } +interface StepperStepAndStatusProps + extends StepperStepProps, + StepperStepStatus {} + +export const StepperStep = React.forwardRef< + HTMLDivElement, + StepperStepAndStatusProps +>((props, ref) => { + const { + children, + description, + icon: CustomIcon, + index, + isCompletedStep, + isCurrentStep, + isLastStep, + label, + optional, + optionalLabel, + } = props + + const { + isVertical, + isError, + isLoading, + successIcon: CustomSuccessIcon, + errorIcon: CustomErrorIcon, + isLabelVertical, + onClickStep, + isClickable, + variant, + } = useStepperContext() + + const hasVisited = isCurrentStep || isCompletedStep + + const handleClick = (index: number) => { + if (isClickable && onClickStep) { + onClickStep(index) } + } - const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon]) + const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon]) - const Success = React.useMemo( - () => CustomSuccessIcon ?? , - [CustomSuccessIcon] - ) + const Success = React.useMemo( + () => CustomSuccessIcon ?? , + [CustomSuccessIcon] + ) - const Error = React.useMemo( - () => CustomErrorIcon ?? , - [CustomErrorIcon] - ) + const Error = React.useMemo(() => CustomErrorIcon ?? , [CustomErrorIcon]) - const RenderIcon = React.useMemo(() => { - if (isCompletedStep) return Success - if (isCurrentStep) { - if (isError) return Error - if (isLoading) return - } - if (Icon) return Icon - return (index || 0) + 1 - }, [ - isCompletedStep, - Success, - isCurrentStep, - Icon, - index, - isError, - Error, - isLoading, - ]) + const RenderIcon = React.useMemo(() => { + if (isCompletedStep) return Success + if (isCurrentStep) { + if (isError) return Error + if (isLoading) return + } + if (Icon) return Icon + return (index || 0) + 1 + }, [ + isCompletedStep, + Success, + isCurrentStep, + Icon, + index, + isError, + Error, + isLoading, + ]) - return ( + return ( +
handleClick(index)} + aria-disabled={!hasVisited} + >
handleClick(index)} - aria-disabled={!hasVisited} + className={cn( + "flex items-center gap-2", + isLabelVertical ? "flex-col" : "" + )} > -
- - -
- - {(isCurrentStep || isCompletedStep) && children} - + {RenderIcon} + +
- ) - } -) + + {(isCurrentStep || isCompletedStep) && children} + +
+ ) +}) -Step.displayName = "Step" +StepperStep.displayName = "StepperStep" -/********** StepLabel **********/ +/********** StepperStepLabel **********/ -interface StepLabelProps { +interface StepperStepLabelProps { label: string | React.ReactNode description?: string | React.ReactNode optional?: boolean optionalLabel?: string | React.ReactNode } -const StepLabel = ({ +const StepperStepLabel = ({ isCurrentStep, label, description, optional, optionalLabel, -}: StepLabelProps & { +}: StepperStepLabelProps & { isCurrentStep?: boolean }) => { - const { isLabelVertical } = useStepsContext() + const { isLabelVertical } = useStepperContext() const shouldRender = !!label || !!description @@ -372,20 +370,21 @@ const StepLabel = ({ ) : null } -StepLabel.displayName = "StepLabel" +StepperStepLabel.displayName = "StepperStepLabel" -/********** Connector **********/ +/********** StepperStepConnector **********/ -interface ConnectorProps extends React.HTMLAttributes { +interface StepperStepConnectorProps + extends React.HTMLAttributes { isCompletedStep: boolean isLastStep?: boolean | null hasLabel?: boolean index: number } -const Connector = React.memo( - ({ isCompletedStep, children, isLastStep }: ConnectorProps) => { - const { isVertical } = useStepsContext() +const StepperStepConnector = React.memo( + ({ isCompletedStep, children, isLastStep }: StepperStepConnectorProps) => { + const { isVertical } = useStepperContext() if (isVertical) { return ( @@ -418,4 +417,4 @@ const Connector = React.memo( } ) -Connector.displayName = "Connector" +StepperStepConnector.displayName = "StepperStepConnector" diff --git a/apps/www/components/ui/use-steps.ts b/apps/www/components/ui/use-stepper.ts similarity index 92% rename from apps/www/components/ui/use-steps.ts rename to apps/www/components/ui/use-stepper.ts index 11cf7e1610e..fef17596fe3 100644 --- a/apps/www/components/ui/use-steps.ts +++ b/apps/www/components/ui/use-stepper.ts @@ -1,16 +1,16 @@ import * as React from "react" -import { StepProps } from "./stepper" +import { StepperStepProps } from "./stepper" -type UseSteps = { +type UseStepper = { initialStep: number steps: Pick< - StepProps, + StepperStepProps, "label" | "description" | "optional" | "optionalLabel" | "icon" >[] } -type UseStepsReturn = { +type UseStepperReturn = { nextStep: () => void prevStep: () => void resetSteps: () => void @@ -21,7 +21,10 @@ type UseStepsReturn = { isOptionalStep: boolean | undefined } -export function useSteps({ initialStep, steps }: UseSteps): UseStepsReturn { +export function useStepper({ + initialStep, + steps, +}: UseStepper): UseStepperReturn { const [activeStep, setActiveStep] = React.useState(initialStep) const nextStep = () => { diff --git a/apps/www/config/components.ts b/apps/www/config/components.ts index 8e4cbb0d456..933cbd3ddbc 100644 --- a/apps/www/config/components.ts +++ b/apps/www/config/components.ts @@ -170,7 +170,7 @@ export const components = [ { component: "stepper", name: "Stepper", - files: ["components/ui/stepper.tsx", "components/ui/use-steps.ts"], + files: ["components/ui/stepper.tsx", "components/ui/use-stepper.ts"], }, { component: "switch", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 9209ed62e14..ae347829253 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -23,9 +23,9 @@ npx shadcn-ui add stepper -4. Copy and paste the `useSteps` hook into your project. +4. Copy and paste the `useStepper` hook into your project. - + @@ -35,15 +35,20 @@ npx shadcn-ui add stepper ## Usage ```tsx -import { Step, Steps, useSteps } from "@/components/ui/stepper" +import { + Stepper, + StepperConfig, + StepperStep, + useStepper, +} from "@/components/ui/stepper" ``` -```tsx {1,4-16} +```tsx {1-5,8-20} const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperDemo = () => { const { @@ -55,22 +60,22 @@ export const StepperDemo = () => { isDisabledStep, isLastStep, isOptionalStep, - } = useSteps({ + } = useStepper({ initialStep: 0, steps, }) return ( <> - + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> @@ -95,10 +100,10 @@ export const StepperDemo = () => { ## API -### `` +### Stepper ```tsx -interface StepsProps { +interface StepperProps { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -112,42 +117,44 @@ interface StepsProps { } ``` -### `` +### StepperStep ```tsx -interface StepLabelProps { +interface StepperStepLabelProps { label: string | React.ReactNode description?: string | React.ReactNode optional?: boolean optionalLabel?: string | React.ReactNode } -interface StepConfig extends StepLabelProps { +interface StepperConfig extends StepperStepLabelProps { icon?: React.ReactElement } -interface StepProps +interface StepperStepProps extends React.HTMLAttributes, VariantProps, StepConfig { isCompletedStep?: boolean } -interface StepStatus { +interface StepperStepStatus { index: number isCompletedStep?: boolean isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus {} +interface StepperStepAndStatusProps + extends StepperStepProps, + StepperStepStatus {} ``` -### `useSteps` +### useStepper -`useSteps` returns values and functions that allow to render the stepper in a modular way. +`useStepper` returns values and functions that allow to render the stepper in a modular way. ```tsx -function useSteps( +function useStepper( initialStep: number, steps: { label: string | React.ReactNode From 4dda819978bd669c25ac280e5a8ae5ca5b82c4ce Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 8 May 2023 13:09:38 -0300 Subject: [PATCH 03/62] feat(stepper): add component with docs --- apps/www/__registry__/index.tsx | 1299 ++++++++++++----- apps/www/config/docs.ts | 6 + apps/www/content/docs/components/stepper.mdx | 271 ++++ apps/www/pages/api/components.json | 10 + .../example/stepper-clickable-steps.tsx | 56 + .../default/example/stepper-custom-icons.tsx | 57 + .../stepper-custom-success-error-icon.tsx | 61 + .../registry/default/example/stepper-demo.tsx | 55 + .../default/example/stepper-descriptions.tsx | 55 + .../example/stepper-label-orientation.tsx | 55 + .../example/stepper-optional-steps.tsx | 55 + .../default/example/stepper-orientation.tsx | 55 + .../default/example/stepper-states.tsx | 76 + apps/www/registry/default/ui/stepper.tsx | 421 ++++++ apps/www/registry/default/ui/use-stepper.ts | 124 ++ .../example/stepper-clickable-steps.tsx | 56 + .../new-york/example/stepper-custom-icons.tsx | 57 + .../stepper-custom-success-error-icon.tsx | 61 + .../new-york/example/stepper-demo.tsx | 55 + .../new-york/example/stepper-descriptions.tsx | 55 + .../example/stepper-label-orientation.tsx | 55 + .../example/stepper-optional-steps.tsx | 55 + .../new-york/example/stepper-orientation.tsx | 55 + .../new-york/example/stepper-states.tsx | 76 + apps/www/registry/new-york/ui/stepper.tsx | 421 ++++++ apps/www/registry/new-york/ui/use-stepper.ts | 124 ++ apps/www/registry/registry.ts | 53 + 27 files changed, 3444 insertions(+), 335 deletions(-) create mode 100644 apps/www/content/docs/components/stepper.mdx create mode 100644 apps/www/registry/default/example/stepper-clickable-steps.tsx create mode 100644 apps/www/registry/default/example/stepper-custom-icons.tsx create mode 100644 apps/www/registry/default/example/stepper-custom-success-error-icon.tsx create mode 100644 apps/www/registry/default/example/stepper-demo.tsx create mode 100644 apps/www/registry/default/example/stepper-descriptions.tsx create mode 100644 apps/www/registry/default/example/stepper-label-orientation.tsx create mode 100644 apps/www/registry/default/example/stepper-optional-steps.tsx create mode 100644 apps/www/registry/default/example/stepper-orientation.tsx create mode 100644 apps/www/registry/default/example/stepper-states.tsx create mode 100644 apps/www/registry/default/ui/stepper.tsx create mode 100644 apps/www/registry/default/ui/use-stepper.ts create mode 100644 apps/www/registry/new-york/example/stepper-clickable-steps.tsx create mode 100644 apps/www/registry/new-york/example/stepper-custom-icons.tsx create mode 100644 apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx create mode 100644 apps/www/registry/new-york/example/stepper-demo.tsx create mode 100644 apps/www/registry/new-york/example/stepper-descriptions.tsx create mode 100644 apps/www/registry/new-york/example/stepper-label-orientation.tsx create mode 100644 apps/www/registry/new-york/example/stepper-optional-steps.tsx create mode 100644 apps/www/registry/new-york/example/stepper-orientation.tsx create mode 100644 apps/www/registry/new-york/example/stepper-states.tsx create mode 100644 apps/www/registry/new-york/ui/stepper.tsx create mode 100644 apps/www/registry/new-york/ui/use-stepper.ts diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 4996505a07d..3f4832e677c 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -4,15 +4,15 @@ import * as React from "react" export const Index: Record = { - "default": { - "accordion": { + default: { + accordion: { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/accordion")), files: ["registry/default/ui/accordion.tsx"], }, - "alert": { + alert: { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -33,56 +33,56 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/aspect-ratio")), files: ["registry/default/ui/aspect-ratio.tsx"], }, - "avatar": { + avatar: { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/avatar")), files: ["registry/default/ui/avatar.tsx"], }, - "badge": { + badge: { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/badge")), files: ["registry/default/ui/badge.tsx"], }, - "button": { + button: { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/button")), files: ["registry/default/ui/button.tsx"], }, - "calendar": { + calendar: { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/calendar")), files: ["registry/default/ui/calendar.tsx"], }, - "card": { + card: { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/card")), files: ["registry/default/ui/card.tsx"], }, - "checkbox": { + checkbox: { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/checkbox")), files: ["registry/default/ui/checkbox.tsx"], }, - "collapsible": { + collapsible: { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/collapsible")), files: ["registry/default/ui/collapsible.tsx"], }, - "command": { + command: { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -96,7 +96,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/context-menu")), files: ["registry/default/ui/context-menu.tsx"], }, - "dialog": { + dialog: { name: "dialog", type: "components:ui", registryDependencies: undefined, @@ -107,13 +107,15 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/ui/dropdown-menu")), + component: React.lazy( + () => import("@/registry/default/ui/dropdown-menu") + ), files: ["registry/default/ui/dropdown-menu.tsx"], }, - "form": { + form: { name: "form", type: "components:ui", - registryDependencies: ["button","label"], + registryDependencies: ["button", "label"], component: React.lazy(() => import("@/registry/default/ui/form")), files: ["registry/default/ui/form.tsx"], }, @@ -124,21 +126,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/hover-card")), files: ["registry/default/ui/hover-card.tsx"], }, - "input": { + input: { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/input")), files: ["registry/default/ui/input.tsx"], }, - "label": { + label: { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/label")), files: ["registry/default/ui/label.tsx"], }, - "menubar": { + menubar: { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -149,17 +151,19 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/ui/navigation-menu")), + component: React.lazy( + () => import("@/registry/default/ui/navigation-menu") + ), files: ["registry/default/ui/navigation-menu.tsx"], }, - "popover": { + popover: { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/popover")), files: ["registry/default/ui/popover.tsx"], }, - "progress": { + progress: { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -180,84 +184,95 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/scroll-area")), files: ["registry/default/ui/scroll-area.tsx"], }, - "select": { + select: { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/select")), files: ["registry/default/ui/select.tsx"], }, - "separator": { + separator: { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/separator")), files: ["registry/default/ui/separator.tsx"], }, - "sheet": { + sheet: { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sheet")), files: ["registry/default/ui/sheet.tsx"], }, - "skeleton": { + skeleton: { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/skeleton")), files: ["registry/default/ui/skeleton.tsx"], }, - "slider": { + slider: { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/slider")), files: ["registry/default/ui/slider.tsx"], }, - "switch": { + stepper: { + name: "stepper", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/stepper")), + files: ["registry/default/ui/stepper.tsx"], + }, + switch: { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/switch")), files: ["registry/default/ui/switch.tsx"], }, - "table": { + table: { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/table")), files: ["registry/default/ui/table.tsx"], }, - "tabs": { + tabs: { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/tabs")), files: ["registry/default/ui/tabs.tsx"], }, - "textarea": { + textarea: { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/textarea")), files: ["registry/default/ui/textarea.tsx"], }, - "toast": { + toast: { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/toast")), - files: ["registry/default/ui/toast.tsx","registry/default/ui/use-toast.ts","registry/default/ui/toaster.tsx"], + files: [ + "registry/default/ui/toast.tsx", + "registry/default/ui/use-toast.ts", + "registry/default/ui/toaster.tsx", + ], }, - "toggle": { + toggle: { name: "toggle", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/toggle")), files: ["registry/default/ui/toggle.tsx"], }, - "tooltip": { + tooltip: { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -268,753 +283,1048 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy(() => import("@/registry/default/example/accordion-demo")), + component: React.lazy( + () => import("@/registry/default/example/accordion-demo") + ), files: ["registry/default/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/default/example/alert-demo")), + component: React.lazy( + () => import("@/registry/default/example/alert-demo") + ), files: ["registry/default/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/default/example/alert-destructive")), + component: React.lazy( + () => import("@/registry/default/example/alert-destructive") + ), files: ["registry/default/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog","button"], - component: React.lazy(() => import("@/registry/default/example/alert-dialog-demo")), + registryDependencies: ["alert-dialog", "button"], + component: React.lazy( + () => import("@/registry/default/example/alert-dialog-demo") + ), files: ["registry/default/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy(() => import("@/registry/default/example/aspect-ratio-demo")), + component: React.lazy( + () => import("@/registry/default/example/aspect-ratio-demo") + ), files: ["registry/default/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy(() => import("@/registry/default/example/avatar-demo")), + component: React.lazy( + () => import("@/registry/default/example/avatar-demo") + ), files: ["registry/default/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-demo")), + component: React.lazy( + () => import("@/registry/default/example/badge-demo") + ), files: ["registry/default/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-destructive")), + component: React.lazy( + () => import("@/registry/default/example/badge-destructive") + ), files: ["registry/default/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-outline")), + component: React.lazy( + () => import("@/registry/default/example/badge-outline") + ), files: ["registry/default/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-secondary")), + component: React.lazy( + () => import("@/registry/default/example/badge-secondary") + ), files: ["registry/default/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-demo")), + component: React.lazy( + () => import("@/registry/default/example/button-demo") + ), files: ["registry/default/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-secondary")), + component: React.lazy( + () => import("@/registry/default/example/button-secondary") + ), files: ["registry/default/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-destructive")), + component: React.lazy( + () => import("@/registry/default/example/button-destructive") + ), files: ["registry/default/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-outline")), + component: React.lazy( + () => import("@/registry/default/example/button-outline") + ), files: ["registry/default/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-ghost")), + component: React.lazy( + () => import("@/registry/default/example/button-ghost") + ), files: ["registry/default/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-link")), + component: React.lazy( + () => import("@/registry/default/example/button-link") + ), files: ["registry/default/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-with-icon")), + component: React.lazy( + () => import("@/registry/default/example/button-with-icon") + ), files: ["registry/default/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-loading")), + component: React.lazy( + () => import("@/registry/default/example/button-loading") + ), files: ["registry/default/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-icon")), + component: React.lazy( + () => import("@/registry/default/example/button-icon") + ), files: ["registry/default/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-as-child")), + component: React.lazy( + () => import("@/registry/default/example/button-as-child") + ), files: ["registry/default/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy(() => import("@/registry/default/example/calendar-demo")), + component: React.lazy( + () => import("@/registry/default/example/calendar-demo") + ), files: ["registry/default/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar","form","popover"], - component: React.lazy(() => import("@/registry/default/example/calendar-form")), + registryDependencies: ["calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/default/example/calendar-form") + ), files: ["registry/default/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card","button","switch"], - component: React.lazy(() => import("@/registry/default/example/card-demo")), + registryDependencies: ["card", "button", "switch"], + component: React.lazy( + () => import("@/registry/default/example/card-demo") + ), files: ["registry/default/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button","card","input","label","select"], - component: React.lazy(() => import("@/registry/default/example/card-with-form")), + registryDependencies: ["button", "card", "input", "label", "select"], + component: React.lazy( + () => import("@/registry/default/example/card-with-form") + ), files: ["registry/default/example/card-with-form.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/default/example/checkbox-demo")), + component: React.lazy( + () => import("@/registry/default/example/checkbox-demo") + ), files: ["registry/default/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/default/example/checkbox-disabled")), + component: React.lazy( + () => import("@/registry/default/example/checkbox-disabled") + ), files: ["registry/default/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/default/example/checkbox-form-multiple")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/default/example/checkbox-form-multiple") + ), files: ["registry/default/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/default/example/checkbox-form-single")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/default/example/checkbox-form-single") + ), files: ["registry/default/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/default/example/checkbox-with-text")), + component: React.lazy( + () => import("@/registry/default/example/checkbox-with-text") + ), files: ["registry/default/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy(() => import("@/registry/default/example/collapsible-demo")), + component: React.lazy( + () => import("@/registry/default/example/collapsible-demo") + ), files: ["registry/default/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/default/example/combobox-demo")), + component: React.lazy( + () => import("@/registry/default/example/combobox-demo") + ), files: ["registry/default/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command","dropdown-menu","button"], - component: React.lazy(() => import("@/registry/default/example/combobox-dropdown-menu")), + registryDependencies: ["command", "dropdown-menu", "button"], + component: React.lazy( + () => import("@/registry/default/example/combobox-dropdown-menu") + ), files: ["registry/default/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command","form"], - component: React.lazy(() => import("@/registry/default/example/combobox-form")), + registryDependencies: ["command", "form"], + component: React.lazy( + () => import("@/registry/default/example/combobox-form") + ), files: ["registry/default/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox","popover"], - component: React.lazy(() => import("@/registry/default/example/combobox-popover")), + registryDependencies: ["combobox", "popover"], + component: React.lazy( + () => import("@/registry/default/example/combobox-popover") + ), files: ["registry/default/example/combobox-popover.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/default/example/command-demo")), + component: React.lazy( + () => import("@/registry/default/example/command-demo") + ), files: ["registry/default/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command","dialog"], - component: React.lazy(() => import("@/registry/default/example/command-dialog")), + registryDependencies: ["command", "dialog"], + component: React.lazy( + () => import("@/registry/default/example/command-dialog") + ), files: ["registry/default/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy(() => import("@/registry/default/example/context-menu-demo")), + component: React.lazy( + () => import("@/registry/default/example/context-menu-demo") + ), files: ["registry/default/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy(() => import("@/registry/default/example/data-table-demo")), + component: React.lazy( + () => import("@/registry/default/example/data-table-demo") + ), files: ["registry/default/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/default/example/date-picker-demo")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-demo") + ), files: ["registry/default/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button","calendar","form","popover"], - component: React.lazy(() => import("@/registry/default/example/date-picker-form")), + registryDependencies: ["button", "calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-form") + ), files: ["registry/default/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button","calendar","popover","select"], - component: React.lazy(() => import("@/registry/default/example/date-picker-with-presets")), + registryDependencies: ["button", "calendar", "popover", "select"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-with-presets") + ), files: ["registry/default/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/default/example/date-picker-with-range")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-with-range") + ), files: ["registry/default/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy(() => import("@/registry/default/example/dialog-demo")), + component: React.lazy( + () => import("@/registry/default/example/dialog-demo") + ), files: ["registry/default/example/dialog-demo.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy(() => import("@/registry/default/example/dropdown-menu-demo")), + component: React.lazy( + () => import("@/registry/default/example/dropdown-menu-demo") + ), files: ["registry/default/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu","checkbox"], - component: React.lazy(() => import("@/registry/default/example/dropdown-menu-checkboxes")), + registryDependencies: ["dropdown-menu", "checkbox"], + component: React.lazy( + () => import("@/registry/default/example/dropdown-menu-checkboxes") + ), files: ["registry/default/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu","radio-group"], - component: React.lazy(() => import("@/registry/default/example/dropdown-menu-radio-group")), + registryDependencies: ["dropdown-menu", "radio-group"], + component: React.lazy( + () => import("@/registry/default/example/dropdown-menu-radio-group") + ), files: ["registry/default/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy(() => import("@/registry/default/example/hover-card-demo")), + component: React.lazy( + () => import("@/registry/default/example/hover-card-demo") + ), files: ["registry/default/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/default/example/input-demo")), + component: React.lazy( + () => import("@/registry/default/example/input-demo") + ), files: ["registry/default/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/default/example/input-disabled")), + component: React.lazy( + () => import("@/registry/default/example/input-disabled") + ), files: ["registry/default/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/default/example/input-file")), + component: React.lazy( + () => import("@/registry/default/example/input-file") + ), files: ["registry/default/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input","button","form"], - component: React.lazy(() => import("@/registry/default/example/input-form")), + registryDependencies: ["input", "button", "form"], + component: React.lazy( + () => import("@/registry/default/example/input-form") + ), files: ["registry/default/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input","button"], - component: React.lazy(() => import("@/registry/default/example/input-with-button")), + registryDependencies: ["input", "button"], + component: React.lazy( + () => import("@/registry/default/example/input-with-button") + ), files: ["registry/default/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/default/example/input-with-label")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/default/example/input-with-label") + ), files: ["registry/default/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/default/example/input-with-text")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/default/example/input-with-text") + ), files: ["registry/default/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy(() => import("@/registry/default/example/label-demo")), + component: React.lazy( + () => import("@/registry/default/example/label-demo") + ), files: ["registry/default/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy(() => import("@/registry/default/example/menubar-demo")), + component: React.lazy( + () => import("@/registry/default/example/menubar-demo") + ), files: ["registry/default/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy(() => import("@/registry/default/example/navigation-menu-demo")), + component: React.lazy( + () => import("@/registry/default/example/navigation-menu-demo") + ), files: ["registry/default/example/navigation-menu-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy(() => import("@/registry/default/example/popover-demo")), + component: React.lazy( + () => import("@/registry/default/example/popover-demo") + ), files: ["registry/default/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy(() => import("@/registry/default/example/progress-demo")), + component: React.lazy( + () => import("@/registry/default/example/progress-demo") + ), files: ["registry/default/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy(() => import("@/registry/default/example/radio-group-demo")), + component: React.lazy( + () => import("@/registry/default/example/radio-group-demo") + ), files: ["registry/default/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group","form"], - component: React.lazy(() => import("@/registry/default/example/radio-group-form")), + registryDependencies: ["radio-group", "form"], + component: React.lazy( + () => import("@/registry/default/example/radio-group-form") + ), files: ["registry/default/example/radio-group-form.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy(() => import("@/registry/default/example/scroll-area-demo")), + component: React.lazy( + () => import("@/registry/default/example/scroll-area-demo") + ), files: ["registry/default/example/scroll-area-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/default/example/select-demo")), + component: React.lazy( + () => import("@/registry/default/example/select-demo") + ), files: ["registry/default/example/select-demo.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/default/example/select-form")), + component: React.lazy( + () => import("@/registry/default/example/select-form") + ), files: ["registry/default/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy(() => import("@/registry/default/example/separator-demo")), + component: React.lazy( + () => import("@/registry/default/example/separator-demo") + ), files: ["registry/default/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/default/example/sheet-demo")), + component: React.lazy( + () => import("@/registry/default/example/sheet-demo") + ), files: ["registry/default/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/default/example/sheet-side")), + component: React.lazy( + () => import("@/registry/default/example/sheet-side") + ), files: ["registry/default/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy(() => import("@/registry/default/example/skeleton-demo")), + component: React.lazy( + () => import("@/registry/default/example/skeleton-demo") + ), files: ["registry/default/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy(() => import("@/registry/default/example/slider-demo")), + component: React.lazy( + () => import("@/registry/default/example/slider-demo") + ), files: ["registry/default/example/slider-demo.tsx"], }, + "stepper-demo": { + name: "stepper-demo", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-demo") + ), + files: ["registry/default/example/stepper-demo.tsx"], + }, + "stepper-orientation": { + name: "stepper-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-orientation") + ), + files: ["registry/default/example/stepper-orientation.tsx"], + }, + "stepper-descriptions": { + name: "stepper-descriptions", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-descriptions") + ), + files: ["registry/default/example/stepper-descriptions.tsx"], + }, + "stepper-custom-icons": { + name: "stepper-custom-icons", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-custom-icons") + ), + files: ["registry/default/example/stepper-custom-icons.tsx"], + }, + "stepper-custom-success-error-icon": { + name: "stepper-custom-success-error-icon", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => + import("@/registry/default/example/stepper-custom-success-error-icon") + ), + files: ["registry/default/example/stepper-custom-success-error-icon.tsx"], + }, + "stepper-label-orientation": { + name: "stepper-label-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-label-orientation") + ), + files: ["registry/default/example/stepper-label-orientation.tsx"], + }, + "stepper-states": { + name: "stepper-states", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-states") + ), + files: ["registry/default/example/stepper-states.tsx"], + }, + "stepper-clickable-steps": { + name: "stepper-clickable-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-clickable-steps") + ), + files: ["registry/default/example/stepper-clickable-steps.tsx"], + }, + "stepper-optional-steps": { + name: "stepper-optional-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/default/example/stepper-optional-steps") + ), + files: ["registry/default/example/stepper-optional-steps.tsx"], + }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy(() => import("@/registry/default/example/switch-demo")), + component: React.lazy( + () => import("@/registry/default/example/switch-demo") + ), files: ["registry/default/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch","form"], - component: React.lazy(() => import("@/registry/default/example/switch-form")), + registryDependencies: ["switch", "form"], + component: React.lazy( + () => import("@/registry/default/example/switch-form") + ), files: ["registry/default/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy(() => import("@/registry/default/example/table-demo")), + component: React.lazy( + () => import("@/registry/default/example/table-demo") + ), files: ["registry/default/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy(() => import("@/registry/default/example/tabs-demo")), + component: React.lazy( + () => import("@/registry/default/example/tabs-demo") + ), files: ["registry/default/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/default/example/textarea-demo")), + component: React.lazy( + () => import("@/registry/default/example/textarea-demo") + ), files: ["registry/default/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/default/example/textarea-disabled")), + component: React.lazy( + () => import("@/registry/default/example/textarea-disabled") + ), files: ["registry/default/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea","form"], - component: React.lazy(() => import("@/registry/default/example/textarea-form")), + registryDependencies: ["textarea", "form"], + component: React.lazy( + () => import("@/registry/default/example/textarea-form") + ), files: ["registry/default/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea","button"], - component: React.lazy(() => import("@/registry/default/example/textarea-with-button")), + registryDependencies: ["textarea", "button"], + component: React.lazy( + () => import("@/registry/default/example/textarea-with-button") + ), files: ["registry/default/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/default/example/textarea-with-label")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/default/example/textarea-with-label") + ), files: ["registry/default/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/default/example/textarea-with-text")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/default/example/textarea-with-text") + ), files: ["registry/default/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-demo")), + component: React.lazy( + () => import("@/registry/default/example/toast-demo") + ), files: ["registry/default/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-destructive")), + component: React.lazy( + () => import("@/registry/default/example/toast-destructive") + ), files: ["registry/default/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-simple")), + component: React.lazy( + () => import("@/registry/default/example/toast-simple") + ), files: ["registry/default/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-with-action")), + component: React.lazy( + () => import("@/registry/default/example/toast-with-action") + ), files: ["registry/default/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-with-title")), + component: React.lazy( + () => import("@/registry/default/example/toast-with-title") + ), files: ["registry/default/example/toast-with-title.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-demo")), + component: React.lazy( + () => import("@/registry/default/example/toggle-demo") + ), files: ["registry/default/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-disabled")), + component: React.lazy( + () => import("@/registry/default/example/toggle-disabled") + ), files: ["registry/default/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-lg")), + component: React.lazy( + () => import("@/registry/default/example/toggle-lg") + ), files: ["registry/default/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-outline")), + component: React.lazy( + () => import("@/registry/default/example/toggle-outline") + ), files: ["registry/default/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-sm")), + component: React.lazy( + () => import("@/registry/default/example/toggle-sm") + ), files: ["registry/default/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-with-text")), + component: React.lazy( + () => import("@/registry/default/example/toggle-with-text") + ), files: ["registry/default/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy(() => import("@/registry/default/example/tooltip-demo")), + component: React.lazy( + () => import("@/registry/default/example/tooltip-demo") + ), files: ["registry/default/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-blockquote")), + component: React.lazy( + () => import("@/registry/default/example/typography-blockquote") + ), files: ["registry/default/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-demo")), + component: React.lazy( + () => import("@/registry/default/example/typography-demo") + ), files: ["registry/default/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h1")), + component: React.lazy( + () => import("@/registry/default/example/typography-h1") + ), files: ["registry/default/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h2")), + component: React.lazy( + () => import("@/registry/default/example/typography-h2") + ), files: ["registry/default/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h3")), + component: React.lazy( + () => import("@/registry/default/example/typography-h3") + ), files: ["registry/default/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h4")), + component: React.lazy( + () => import("@/registry/default/example/typography-h4") + ), files: ["registry/default/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-inline-code")), + component: React.lazy( + () => import("@/registry/default/example/typography-inline-code") + ), files: ["registry/default/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-large")), + component: React.lazy( + () => import("@/registry/default/example/typography-large") + ), files: ["registry/default/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-lead")), + component: React.lazy( + () => import("@/registry/default/example/typography-lead") + ), files: ["registry/default/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-list")), + component: React.lazy( + () => import("@/registry/default/example/typography-list") + ), files: ["registry/default/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-muted")), + component: React.lazy( + () => import("@/registry/default/example/typography-muted") + ), files: ["registry/default/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-p")), + component: React.lazy( + () => import("@/registry/default/example/typography-p") + ), files: ["registry/default/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-small")), + component: React.lazy( + () => import("@/registry/default/example/typography-small") + ), files: ["registry/default/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-table")), + component: React.lazy( + () => import("@/registry/default/example/typography-table") + ), files: ["registry/default/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/mode-toggle")), + component: React.lazy( + () => import("@/registry/default/example/mode-toggle") + ), files: ["registry/default/example/mode-toggle.tsx"], }, - }, "new-york": { - "accordion": { + }, + "new-york": { + accordion: { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/accordion")), files: ["registry/new-york/ui/accordion.tsx"], }, - "alert": { + alert: { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -1025,66 +1335,70 @@ export const Index: Record = { name: "alert-dialog", type: "components:ui", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/ui/alert-dialog")), + component: React.lazy( + () => import("@/registry/new-york/ui/alert-dialog") + ), files: ["registry/new-york/ui/alert-dialog.tsx"], }, "aspect-ratio": { name: "aspect-ratio", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/aspect-ratio")), + component: React.lazy( + () => import("@/registry/new-york/ui/aspect-ratio") + ), files: ["registry/new-york/ui/aspect-ratio.tsx"], }, - "avatar": { + avatar: { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/avatar")), files: ["registry/new-york/ui/avatar.tsx"], }, - "badge": { + badge: { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/badge")), files: ["registry/new-york/ui/badge.tsx"], }, - "button": { + button: { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/button")), files: ["registry/new-york/ui/button.tsx"], }, - "calendar": { + calendar: { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/calendar")), files: ["registry/new-york/ui/calendar.tsx"], }, - "card": { + card: { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/card")), files: ["registry/new-york/ui/card.tsx"], }, - "checkbox": { + checkbox: { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/checkbox")), files: ["registry/new-york/ui/checkbox.tsx"], }, - "collapsible": { + collapsible: { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/collapsible")), files: ["registry/new-york/ui/collapsible.tsx"], }, - "command": { + command: { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -1095,10 +1409,12 @@ export const Index: Record = { name: "context-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/context-menu")), + component: React.lazy( + () => import("@/registry/new-york/ui/context-menu") + ), files: ["registry/new-york/ui/context-menu.tsx"], }, - "dialog": { + dialog: { name: "dialog", type: "components:ui", registryDependencies: undefined, @@ -1109,13 +1425,15 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/dropdown-menu")), + component: React.lazy( + () => import("@/registry/new-york/ui/dropdown-menu") + ), files: ["registry/new-york/ui/dropdown-menu.tsx"], }, - "form": { + form: { name: "form", type: "components:ui", - registryDependencies: ["button","label"], + registryDependencies: ["button", "label"], component: React.lazy(() => import("@/registry/new-york/ui/form")), files: ["registry/new-york/ui/form.tsx"], }, @@ -1126,21 +1444,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/hover-card")), files: ["registry/new-york/ui/hover-card.tsx"], }, - "input": { + input: { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/input")), files: ["registry/new-york/ui/input.tsx"], }, - "label": { + label: { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/label")), files: ["registry/new-york/ui/label.tsx"], }, - "menubar": { + menubar: { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -1151,17 +1469,19 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/navigation-menu")), + component: React.lazy( + () => import("@/registry/new-york/ui/navigation-menu") + ), files: ["registry/new-york/ui/navigation-menu.tsx"], }, - "popover": { + popover: { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/popover")), files: ["registry/new-york/ui/popover.tsx"], }, - "progress": { + progress: { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -1182,84 +1502,95 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/scroll-area")), files: ["registry/new-york/ui/scroll-area.tsx"], }, - "select": { + select: { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/select")), files: ["registry/new-york/ui/select.tsx"], }, - "separator": { + separator: { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/separator")), files: ["registry/new-york/ui/separator.tsx"], }, - "sheet": { + sheet: { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sheet")), files: ["registry/new-york/ui/sheet.tsx"], }, - "skeleton": { + skeleton: { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/skeleton")), files: ["registry/new-york/ui/skeleton.tsx"], }, - "slider": { + slider: { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/slider")), files: ["registry/new-york/ui/slider.tsx"], }, - "switch": { + stepper: { + name: "stepper", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/new-york/ui/stepper")), + files: ["registry/new-york/ui/stepper.tsx"], + }, + switch: { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/switch")), files: ["registry/new-york/ui/switch.tsx"], }, - "table": { + table: { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/table")), files: ["registry/new-york/ui/table.tsx"], }, - "tabs": { + tabs: { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/tabs")), files: ["registry/new-york/ui/tabs.tsx"], }, - "textarea": { + textarea: { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/textarea")), files: ["registry/new-york/ui/textarea.tsx"], }, - "toast": { + toast: { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/toast")), - files: ["registry/new-york/ui/toast.tsx","registry/new-york/ui/use-toast.ts","registry/new-york/ui/toaster.tsx"], + files: [ + "registry/new-york/ui/toast.tsx", + "registry/new-york/ui/use-toast.ts", + "registry/new-york/ui/toaster.tsx", + ], }, - "toggle": { + toggle: { name: "toggle", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/toggle")), files: ["registry/new-york/ui/toggle.tsx"], }, - "tooltip": { + tooltip: { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -1270,742 +1601,1040 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy(() => import("@/registry/new-york/example/accordion-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/accordion-demo") + ), files: ["registry/new-york/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/new-york/example/alert-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/alert-demo") + ), files: ["registry/new-york/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/new-york/example/alert-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/alert-destructive") + ), files: ["registry/new-york/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog","button"], - component: React.lazy(() => import("@/registry/new-york/example/alert-dialog-demo")), + registryDependencies: ["alert-dialog", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/alert-dialog-demo") + ), files: ["registry/new-york/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy(() => import("@/registry/new-york/example/aspect-ratio-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/aspect-ratio-demo") + ), files: ["registry/new-york/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy(() => import("@/registry/new-york/example/avatar-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/avatar-demo") + ), files: ["registry/new-york/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-demo") + ), files: ["registry/new-york/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-destructive") + ), files: ["registry/new-york/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-outline")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-outline") + ), files: ["registry/new-york/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-secondary")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-secondary") + ), files: ["registry/new-york/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/button-demo") + ), files: ["registry/new-york/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-secondary")), + component: React.lazy( + () => import("@/registry/new-york/example/button-secondary") + ), files: ["registry/new-york/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/button-destructive") + ), files: ["registry/new-york/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-outline")), + component: React.lazy( + () => import("@/registry/new-york/example/button-outline") + ), files: ["registry/new-york/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-ghost")), + component: React.lazy( + () => import("@/registry/new-york/example/button-ghost") + ), files: ["registry/new-york/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-link")), + component: React.lazy( + () => import("@/registry/new-york/example/button-link") + ), files: ["registry/new-york/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-with-icon")), + component: React.lazy( + () => import("@/registry/new-york/example/button-with-icon") + ), files: ["registry/new-york/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-loading")), + component: React.lazy( + () => import("@/registry/new-york/example/button-loading") + ), files: ["registry/new-york/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-icon")), + component: React.lazy( + () => import("@/registry/new-york/example/button-icon") + ), files: ["registry/new-york/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-as-child")), + component: React.lazy( + () => import("@/registry/new-york/example/button-as-child") + ), files: ["registry/new-york/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy(() => import("@/registry/new-york/example/calendar-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/calendar-demo") + ), files: ["registry/new-york/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar","form","popover"], - component: React.lazy(() => import("@/registry/new-york/example/calendar-form")), + registryDependencies: ["calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/calendar-form") + ), files: ["registry/new-york/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card","button","switch"], - component: React.lazy(() => import("@/registry/new-york/example/card-demo")), + registryDependencies: ["card", "button", "switch"], + component: React.lazy( + () => import("@/registry/new-york/example/card-demo") + ), files: ["registry/new-york/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button","card","input","label","select"], - component: React.lazy(() => import("@/registry/new-york/example/card-with-form")), + registryDependencies: ["button", "card", "input", "label", "select"], + component: React.lazy( + () => import("@/registry/new-york/example/card-with-form") + ), files: ["registry/new-york/example/card-with-form.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-demo") + ), files: ["registry/new-york/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-disabled") + ), files: ["registry/new-york/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-multiple")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-form-multiple") + ), files: ["registry/new-york/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-single")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-form-single") + ), files: ["registry/new-york/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-with-text")), + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-with-text") + ), files: ["registry/new-york/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy(() => import("@/registry/new-york/example/collapsible-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/collapsible-demo") + ), files: ["registry/new-york/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/combobox-demo") + ), files: ["registry/new-york/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command","dropdown-menu","button"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-dropdown-menu")), + registryDependencies: ["command", "dropdown-menu", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/combobox-dropdown-menu") + ), files: ["registry/new-york/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command","form"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-form")), + registryDependencies: ["command", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/combobox-form") + ), files: ["registry/new-york/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox","popover"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-popover")), + registryDependencies: ["combobox", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/combobox-popover") + ), files: ["registry/new-york/example/combobox-popover.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/new-york/example/command-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/command-demo") + ), files: ["registry/new-york/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command","dialog"], - component: React.lazy(() => import("@/registry/new-york/example/command-dialog")), + registryDependencies: ["command", "dialog"], + component: React.lazy( + () => import("@/registry/new-york/example/command-dialog") + ), files: ["registry/new-york/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy(() => import("@/registry/new-york/example/context-menu-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/context-menu-demo") + ), files: ["registry/new-york/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy(() => import("@/registry/new-york/example/data-table-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/data-table-demo") + ), files: ["registry/new-york/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-demo")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-demo") + ), files: ["registry/new-york/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button","calendar","form","popover"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-form")), + registryDependencies: ["button", "calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-form") + ), files: ["registry/new-york/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button","calendar","popover","select"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-presets")), + registryDependencies: ["button", "calendar", "popover", "select"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-with-presets") + ), files: ["registry/new-york/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-range")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-with-range") + ), files: ["registry/new-york/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy(() => import("@/registry/new-york/example/dialog-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/dialog-demo") + ), files: ["registry/new-york/example/dialog-demo.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/dropdown-menu-demo") + ), files: ["registry/new-york/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu","checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-checkboxes")), + registryDependencies: ["dropdown-menu", "checkbox"], + component: React.lazy( + () => import("@/registry/new-york/example/dropdown-menu-checkboxes") + ), files: ["registry/new-york/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu","radio-group"], - component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-radio-group")), + registryDependencies: ["dropdown-menu", "radio-group"], + component: React.lazy( + () => import("@/registry/new-york/example/dropdown-menu-radio-group") + ), files: ["registry/new-york/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy(() => import("@/registry/new-york/example/hover-card-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/hover-card-demo") + ), files: ["registry/new-york/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/new-york/example/input-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/input-demo") + ), files: ["registry/new-york/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/new-york/example/input-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/input-disabled") + ), files: ["registry/new-york/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/new-york/example/input-file")), + component: React.lazy( + () => import("@/registry/new-york/example/input-file") + ), files: ["registry/new-york/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input","button","form"], - component: React.lazy(() => import("@/registry/new-york/example/input-form")), + registryDependencies: ["input", "button", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/input-form") + ), files: ["registry/new-york/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input","button"], - component: React.lazy(() => import("@/registry/new-york/example/input-with-button")), + registryDependencies: ["input", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/input-with-button") + ), files: ["registry/new-york/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/new-york/example/input-with-label")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/input-with-label") + ), files: ["registry/new-york/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/new-york/example/input-with-text")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/input-with-text") + ), files: ["registry/new-york/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy(() => import("@/registry/new-york/example/label-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/label-demo") + ), files: ["registry/new-york/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy(() => import("@/registry/new-york/example/menubar-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/menubar-demo") + ), files: ["registry/new-york/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy(() => import("@/registry/new-york/example/navigation-menu-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/navigation-menu-demo") + ), files: ["registry/new-york/example/navigation-menu-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy(() => import("@/registry/new-york/example/popover-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/popover-demo") + ), files: ["registry/new-york/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy(() => import("@/registry/new-york/example/progress-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/progress-demo") + ), files: ["registry/new-york/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy(() => import("@/registry/new-york/example/radio-group-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/radio-group-demo") + ), files: ["registry/new-york/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group","form"], - component: React.lazy(() => import("@/registry/new-york/example/radio-group-form")), + registryDependencies: ["radio-group", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/radio-group-form") + ), files: ["registry/new-york/example/radio-group-form.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy(() => import("@/registry/new-york/example/scroll-area-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/scroll-area-demo") + ), files: ["registry/new-york/example/scroll-area-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/new-york/example/select-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/select-demo") + ), files: ["registry/new-york/example/select-demo.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/new-york/example/select-form")), + component: React.lazy( + () => import("@/registry/new-york/example/select-form") + ), files: ["registry/new-york/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy(() => import("@/registry/new-york/example/separator-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/separator-demo") + ), files: ["registry/new-york/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/new-york/example/sheet-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/sheet-demo") + ), files: ["registry/new-york/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/new-york/example/sheet-side")), + component: React.lazy( + () => import("@/registry/new-york/example/sheet-side") + ), files: ["registry/new-york/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy(() => import("@/registry/new-york/example/skeleton-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/skeleton-demo") + ), files: ["registry/new-york/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy(() => import("@/registry/new-york/example/slider-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/slider-demo") + ), files: ["registry/new-york/example/slider-demo.tsx"], }, + "stepper-demo": { + name: "stepper-demo", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-demo") + ), + files: ["registry/new-york/example/stepper-demo.tsx"], + }, + "stepper-orientation": { + name: "stepper-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-orientation") + ), + files: ["registry/new-york/example/stepper-orientation.tsx"], + }, + "stepper-descriptions": { + name: "stepper-descriptions", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-descriptions") + ), + files: ["registry/new-york/example/stepper-descriptions.tsx"], + }, + "stepper-custom-icons": { + name: "stepper-custom-icons", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-custom-icons") + ), + files: ["registry/new-york/example/stepper-custom-icons.tsx"], + }, + "stepper-custom-success-error-icon": { + name: "stepper-custom-success-error-icon", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => + import( + "@/registry/new-york/example/stepper-custom-success-error-icon" + ) + ), + files: [ + "registry/new-york/example/stepper-custom-success-error-icon.tsx", + ], + }, + "stepper-label-orientation": { + name: "stepper-label-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-label-orientation") + ), + files: ["registry/new-york/example/stepper-label-orientation.tsx"], + }, + "stepper-states": { + name: "stepper-states", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-states") + ), + files: ["registry/new-york/example/stepper-states.tsx"], + }, + "stepper-clickable-steps": { + name: "stepper-clickable-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-clickable-steps") + ), + files: ["registry/new-york/example/stepper-clickable-steps.tsx"], + }, + "stepper-optional-steps": { + name: "stepper-optional-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-optional-steps") + ), + files: ["registry/new-york/example/stepper-optional-steps.tsx"], + }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy(() => import("@/registry/new-york/example/switch-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/switch-demo") + ), files: ["registry/new-york/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch","form"], - component: React.lazy(() => import("@/registry/new-york/example/switch-form")), + registryDependencies: ["switch", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/switch-form") + ), files: ["registry/new-york/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy(() => import("@/registry/new-york/example/table-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/table-demo") + ), files: ["registry/new-york/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy(() => import("@/registry/new-york/example/tabs-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/tabs-demo") + ), files: ["registry/new-york/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/textarea-demo") + ), files: ["registry/new-york/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/textarea-disabled") + ), files: ["registry/new-york/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea","form"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-form")), + registryDependencies: ["textarea", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-form") + ), files: ["registry/new-york/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea","button"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-with-button")), + registryDependencies: ["textarea", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-with-button") + ), files: ["registry/new-york/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-with-label")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-with-label") + ), files: ["registry/new-york/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-with-text")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-with-text") + ), files: ["registry/new-york/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-demo") + ), files: ["registry/new-york/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-destructive") + ), files: ["registry/new-york/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-simple")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-simple") + ), files: ["registry/new-york/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-with-action")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-with-action") + ), files: ["registry/new-york/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-with-title")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-with-title") + ), files: ["registry/new-york/example/toast-with-title.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-demo") + ), files: ["registry/new-york/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-disabled") + ), files: ["registry/new-york/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-lg")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-lg") + ), files: ["registry/new-york/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-outline")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-outline") + ), files: ["registry/new-york/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-sm")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-sm") + ), files: ["registry/new-york/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-with-text")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-with-text") + ), files: ["registry/new-york/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy(() => import("@/registry/new-york/example/tooltip-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/tooltip-demo") + ), files: ["registry/new-york/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-blockquote")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-blockquote") + ), files: ["registry/new-york/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-demo") + ), files: ["registry/new-york/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h1")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h1") + ), files: ["registry/new-york/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h2")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h2") + ), files: ["registry/new-york/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h3")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h3") + ), files: ["registry/new-york/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h4")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h4") + ), files: ["registry/new-york/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-inline-code")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-inline-code") + ), files: ["registry/new-york/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-large")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-large") + ), files: ["registry/new-york/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-lead")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-lead") + ), files: ["registry/new-york/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-list")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-list") + ), files: ["registry/new-york/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-muted")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-muted") + ), files: ["registry/new-york/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-p")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-p") + ), files: ["registry/new-york/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-small")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-small") + ), files: ["registry/new-york/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-table")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-table") + ), files: ["registry/new-york/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/mode-toggle")), + component: React.lazy( + () => import("@/registry/new-york/example/mode-toggle") + ), files: ["registry/new-york/example/mode-toggle.tsx"], }, }, diff --git a/apps/www/config/docs.ts b/apps/www/config/docs.ts index c5a61801710..7bdc3b62f77 100644 --- a/apps/www/config/docs.ts +++ b/apps/www/config/docs.ts @@ -283,6 +283,12 @@ export const docsConfig: DocsConfig = { href: "/docs/components/slider", items: [], }, + { + title: "Stepper", + href: "/docs/components/stepper", + label: "New", + items: [], + }, { title: "Switch", href: "/docs/components/switch", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx new file mode 100644 index 00000000000..ec82ceb446d --- /dev/null +++ b/apps/www/content/docs/components/stepper.mdx @@ -0,0 +1,271 @@ +--- +title: Stepper +description: Display content divided into a steps sequence. +component: true +--- + + + +## Installation + + + + + CLI + Manual + + + + + +Run the following command: + +```bash +npx shadcn-ui@latest add stepper +``` + +Use the Stepper component with useStepper hook + +```tsx title="page.tsx" {1,9} +"use client" + +import { useStepper } from "@/components/ui/use-stepper" + +export default function Page() { + const { currentStep, nextStep, prevStep, ...rest } = useStepper() + return ( + // ... + ) +} +``` + + + + + + + + + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + +Use the Stepper component with useStepper hook + +```tsx title="page.tsx" {1,9} +"use client" + +import { useStepper } from "@/components/ui/use-stepper" + +export default function Page() { + const { currentStep, nextStep, prevStep, ...rest } = useStepper() + return ( + // ... + ) +} +``` + + + + + + + +## Usage + +```tsx +import { Step, Steps, useStepper } from "@/components/ui/stepper" +``` + +```tsx {1-5} +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export const StepperDemo = () => { + const { + nextStep, + prevStep, + resetSteps, + setStep, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( + <> + + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+ + ) +} +``` + +## API + +### `` + +```tsx +interface StepsProps { + activeStep: number + orientation?: "vertical" | "horizontal" + state?: "loading" | "error" + responsive?: boolean + onClickStep?: (step: number) => void + successIcon?: React.ReactElement + errorIcon?: React.ReactElement + labelOrientation?: "vertical" | "horizontal" + children?: React.ReactNode + variant?: "default" | "ghost" | "outline" | "secondary" +} +``` + +### `` + +```tsx +interface StepLabelProps { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + optionalLabel?: string | React.ReactNode +} + +interface StepConfig extends StepLabelProps { + icon?: React.ReactElement +} + +interface StepProps + extends React.HTMLAttributes, + VariantProps, + StepConfig { + isCompletedStep?: boolean +} + +interface StepStatus { + index: number + isCompletedStep?: boolean + isCurrentStep?: boolean +} + +interface StepAndStatusProps extends StepProps, StepStatus {} +``` + +### `useStepper` + +`useStepper` returns values and functions that allow to render the stepper in a modular way. + +```tsx +function useStepper( + initialStep: number, + steps: { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + optionalLabel?: string | React.ReactNode + icon?: React.ReactElement + }[] +): { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number + isDisabledStep: boolean + isLastStep: boolean + isOptionalStep: boolean | undefined +} +``` + +## Examples + +### Default + + + +--- + +### Orientation + +We can pass the `orientation` prop to the Steps component to set the orientation as "vertical" or "horizontal". + + + +### Descriptions + +The Step component also accepts a `description` prop which can be used to provide some extra information about the step. + + + +### Custom Icons + +If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component. + + + +### Custom Success/Error icon + +If you want to show a custom success/error icon instead of the default, you can do so by using the `successIcon` and `errorIcon` prop on the Steps component. + + + +### Label orientation + +If you would like the labels to be positioned below the step icons you can do so using the `labelOrientation` prop on the Steps component. + + + +### States + +Sometimes it is useful to show visual feedback to the user depending on some asynchronous logic. In this case we can use the `state` prop. + + + +### Clickable Steps + +By providing the onClickStep prop the steps will become clickable. + + + +### Optional Steps + +If you want to make a step optional, you can use the value of optional and optionalLabel in the array of steps. + + + +### Responsive + +By default, the stepper behaves vertically on small screens. If you need to remove that functionality, you can change the `responsive` prop to false in the Steps component. diff --git a/apps/www/pages/api/components.json b/apps/www/pages/api/components.json index cf89b8f3f4f..6e52e77d00a 100644 --- a/apps/www/pages/api/components.json +++ b/apps/www/pages/api/components.json @@ -343,6 +343,16 @@ } ], "type": "ui" + }, + { + "component": "stepper", + "name": "Stepper", + "files": [ + { + "name": "stepper.tsx", + "dir": "components/ui" + } + ] }, { "name": "switch", diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx new file mode 100644 index 00000000000..304c9bc8143 --- /dev/null +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -0,0 +1,56 @@ +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperClickableSteps() { + const { + nextStep, + prevStep, + setStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ setStep(step)} activeStep={activeStep}> + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx new file mode 100644 index 00000000000..c3f6e0cfb78 --- /dev/null +++ b/apps/www/registry/default/example/stepper-custom-icons.tsx @@ -0,0 +1,57 @@ +import { Calendar, Twitter, User } from "lucide-react" + +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1", icon: }, + { label: "Step 2", icon: }, + { label: "Step 3", icon: }, +] satisfies StepConfig[] + +export default function StepperCustomIcons() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx b/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx new file mode 100644 index 00000000000..829bb821aac --- /dev/null +++ b/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx @@ -0,0 +1,61 @@ +import { CheckCircle2, XCircle } from "lucide-react" + +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperCustomSuccessErrorIcon() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ } + errorIcon={} + > + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx new file mode 100644 index 00000000000..e37129d9fe7 --- /dev/null +++ b/apps/www/registry/default/example/stepper-demo.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperDemo() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-descriptions.tsx b/apps/www/registry/default/example/stepper-descriptions.tsx new file mode 100644 index 00000000000..e84ec084a79 --- /dev/null +++ b/apps/www/registry/default/example/stepper-descriptions.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1", description: "Frist description" }, + { label: "Step 2", description: "Second description" }, + { label: "Step 3", description: "Third description" }, +] satisfies StepConfig[] + +export default function StepperDescriptions() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-label-orientation.tsx b/apps/www/registry/default/example/stepper-label-orientation.tsx new file mode 100644 index 00000000000..da8789b6a55 --- /dev/null +++ b/apps/www/registry/default/example/stepper-label-orientation.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1", description: "Frist description" }, + { label: "Step 2", description: "Second description" }, + { label: "Step 3", description: "Third description" }, +] satisfies StepConfig[] + +export default function StepperLabelOrientation() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx new file mode 100644 index 00000000000..572837fa7ea --- /dev/null +++ b/apps/www/registry/default/example/stepper-optional-steps.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2", optional: true, optionalLabel: "Optional" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperOptionalSteps() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx new file mode 100644 index 00000000000..ce21fc03a9e --- /dev/null +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/default/ui/button" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperOrientation() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/example/stepper-states.tsx b/apps/www/registry/default/example/stepper-states.tsx new file mode 100644 index 00000000000..12d43939781 --- /dev/null +++ b/apps/www/registry/default/example/stepper-states.tsx @@ -0,0 +1,76 @@ +import { useState } from "react" + +import { Button } from "@/registry/default/ui/button" +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" +import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { useStepper } from "@/registry/default/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperStates() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + const [value, setValue] = useState<"loading" | "error">("loading") + + return ( +
+ setValue(value as "loading" | "error")} + className="mb-4" + > +
+ + +
+
+ + +
+
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx new file mode 100644 index 00000000000..f1171654f0c --- /dev/null +++ b/apps/www/registry/default/ui/stepper.tsx @@ -0,0 +1,421 @@ +import * as React from "react" +import { VariantProps, cva } from "class-variance-authority" +import { Check, Loader2, X } from "lucide-react" + +import { cn } from "@/lib/utils" + +import { Button } from "./button" +import { Separator } from "./separator" +import { useMediaQuery } from "./use-stepper" + +/********** Context **********/ + +interface StepsContextValue extends StepsProps { + isClickable?: boolean + isError?: boolean + isLoading?: boolean + isVertical?: boolean + isLabelVertical?: boolean + stepCount?: number +} + +const StepsContext = React.createContext({ + activeStep: 0, +}) + +export const useStepperContext = () => React.useContext(StepsContext) + +export const StepsProvider: React.FC<{ + value: StepsContextValue + children: React.ReactNode +}> = ({ value, children }) => { + const isError = value.state === "error" + const isLoading = value.state === "loading" + + const isVertical = value.orientation === "vertical" + const isLabelVertical = + value.orientation !== "vertical" && value.labelOrientation === "vertical" + + return ( + + {children} + + ) +} + +/********** Steps **********/ + +export interface StepsProps { + activeStep: number + orientation?: "vertical" | "horizontal" + state?: "loading" | "error" + responsive?: boolean + onClickStep?: (step: number) => void + successIcon?: React.ReactElement + errorIcon?: React.ReactElement + labelOrientation?: "vertical" | "horizontal" + children?: React.ReactNode + variant?: "default" | "ghost" | "outline" | "secondary" +} + +export const Steps = React.forwardRef( + ( + { + activeStep = 0, + state, + responsive = true, + orientation: orientationProp = "horizontal", + onClickStep, + labelOrientation = "horizontal", + children, + errorIcon, + successIcon, + variant = "default", + }, + ref + ) => { + const childArr = React.Children.toArray(children) + + const stepCount = childArr.length + + const renderHorizontalContent = () => { + if (activeStep <= childArr.length) { + return React.Children.map(childArr[activeStep], (node) => { + if (!React.isValidElement(node)) return + return React.Children.map( + node.props.children, + (childNode) => childNode + ) + }) + } + return null + } + + const isClickable = !!onClickStep + + const isMobile = useMediaQuery("(max-width: 43em)") + + const orientation = isMobile && responsive ? "vertical" : orientationProp + + return ( + +
+ {React.Children.map(children, (child, i) => { + const isCompletedStep = + (React.isValidElement(child) && child.props.isCompletedStep) ?? + i < activeStep + const isLastStep = i === stepCount - 1 + const isCurrentStep = i === activeStep + + const stepProps = { + index: i, + isCompletedStep, + isCurrentStep, + isLastStep, + } + + if (React.isValidElement(child)) { + return React.cloneElement(child, stepProps) + } + + return null + })} +
+ {orientation === "horizontal" && renderHorizontalContent()} +
+ ) + } +) + +Steps.displayName = "Steps" + +/********** Step **********/ + +const stepVariants = cva("relative flex flex-row gap-2", { + variants: { + isLastStep: { + true: "flex-[0_0_auto] justify-end", + false: "flex-[1_0_auto] justify-start", + }, + isVertical: { + true: "flex-col", + false: "items-center", + }, + isClickable: { + true: "cursor-pointer", + }, + }, + compoundVariants: [ + { + isVertical: true, + isLastStep: true, + class: "w-full flex-[1_0_auto] flex-col items-start justify-start", + }, + ], +}) + +export interface StepConfig extends StepLabelProps { + icon?: React.ReactElement +} + +export interface StepProps + extends React.HTMLAttributes, + VariantProps, + StepConfig { + isCompletedStep?: boolean +} + +interface StepStatus { + index: number + isCompletedStep?: boolean + isCurrentStep?: boolean +} + +interface StepAndStatusProps extends StepProps, StepStatus {} + +export const Step = React.forwardRef( + (props, ref) => { + const { + children, + description, + icon: CustomIcon, + index, + isCompletedStep, + isCurrentStep, + isLastStep, + label, + optional, + optionalLabel, + } = props + + const { + isVertical, + isError, + isLoading, + successIcon: CustomSuccessIcon, + errorIcon: CustomErrorIcon, + isLabelVertical, + onClickStep, + isClickable, + variant, + } = useStepperContext() + + const hasVisited = isCurrentStep || isCompletedStep + + const handleClick = (index: number) => { + if (isClickable && onClickStep) { + onClickStep(index) + } + } + + const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon]) + + const Success = React.useMemo( + () => CustomSuccessIcon ?? , + [CustomSuccessIcon] + ) + + const Error = React.useMemo( + () => CustomErrorIcon ?? , + [CustomErrorIcon] + ) + + const RenderIcon = React.useMemo(() => { + if (isCompletedStep) return Success + if (isCurrentStep) { + if (isError) return Error + if (isLoading) return + } + if (Icon) return Icon + return (index || 0) + 1 + }, [ + isCompletedStep, + Success, + isCurrentStep, + Icon, + index, + isError, + Error, + isLoading, + ]) + + return ( +
handleClick(index)} + aria-disabled={!hasVisited} + > +
+ + +
+ + {(isCurrentStep || isCompletedStep) && children} + +
+ ) + } +) + +Step.displayName = "Step" + +/********** StepLabel **********/ + +interface StepLabelProps { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + optionalLabel?: string | React.ReactNode +} + +const StepLabel = ({ + isCurrentStep, + label, + description, + optional, + optionalLabel, +}: StepLabelProps & { + isCurrentStep?: boolean +}) => { + const { isLabelVertical } = useStepperContext() + + const shouldRender = !!label || !!description + + const renderOptionalLabel = !!optional && !!optionalLabel + + return shouldRender ? ( +
+ {!!label && ( +

+ {label} + {renderOptionalLabel && ( + + ({optionalLabel}) + + )} +

+ )} + {!!description && ( +

{description}

+ )} +
+ ) : null +} + +StepLabel.displayName = "StepLabel" + +/********** Connector **********/ + +interface ConnectorProps extends React.HTMLAttributes { + isCompletedStep: boolean + isLastStep?: boolean | null + hasLabel?: boolean + index: number +} + +const Connector = React.memo( + ({ isCompletedStep, children, isLastStep }: ConnectorProps) => { + const { isVertical } = useStepperContext() + + if (isVertical) { + return ( +
+ {!isCompletedStep && ( +
{children}
+ )} +
+ ) + } + + if (isLastStep) { + return null + } + + return ( + + ) + } +) + +Connector.displayName = "Connector" diff --git a/apps/www/registry/default/ui/use-stepper.ts b/apps/www/registry/default/ui/use-stepper.ts new file mode 100644 index 00000000000..01d2163bcad --- /dev/null +++ b/apps/www/registry/default/ui/use-stepper.ts @@ -0,0 +1,124 @@ +import * as React from "react" + +import { StepProps } from "./stepper" + +type useStepper = { + initialStep: number + steps: Pick< + StepProps, + "label" | "description" | "optional" | "optionalLabel" | "icon" + >[] +} + +type useStepperReturn = { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number + isDisabledStep: boolean + isLastStep: boolean + isOptionalStep: boolean | undefined +} + +export function useStepper({ + initialStep, + steps, +}: useStepper): useStepperReturn { + const [activeStep, setActiveStep] = React.useState(initialStep) + + const nextStep = () => { + setActiveStep((prev) => prev + 1) + } + + const prevStep = () => { + setActiveStep((prev) => prev - 1) + } + + const resetSteps = () => { + setActiveStep(initialStep) + } + + const setStep = (step: number) => { + setActiveStep(step) + } + + const isDisabledStep = activeStep === 0 + + const isLastStep = activeStep === steps.length - 1 + + const isOptionalStep = steps[activeStep]?.optional + + return { + nextStep, + prevStep, + resetSteps, + setStep, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } +} + +interface UseMediaQueryOptions { + getInitialValueInEffect: boolean +} + +type MediaQueryCallback = (event: { matches: boolean; media: string }) => void + +/** + * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia + * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent + * */ +function attachMediaListener( + query: MediaQueryList, + callback: MediaQueryCallback +) { + try { + query.addEventListener("change", callback) + return () => query.removeEventListener("change", callback) + } catch (e) { + query.addListener(callback) + return () => query.removeListener(callback) + } +} + +function getInitialValue(query: string, initialValue?: boolean) { + if (typeof initialValue === "boolean") { + return initialValue + } + + if (typeof window !== "undefined" && "matchMedia" in window) { + return window.matchMedia(query).matches + } + + return false +} + +export function useMediaQuery( + query: string, + initialValue?: boolean, + { getInitialValueInEffect }: UseMediaQueryOptions = { + getInitialValueInEffect: true, + } +) { + const [matches, setMatches] = React.useState( + getInitialValueInEffect ? false : getInitialValue(query, initialValue) + ) + const queryRef = React.useRef() + + React.useEffect(() => { + if ("matchMedia" in window) { + queryRef.current = window.matchMedia(query) + setMatches(queryRef.current.matches) + return attachMediaListener(queryRef.current, (event) => + setMatches(event.matches) + ) + } + + return undefined + }, [query]) + + return matches +} diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx new file mode 100644 index 00000000000..fca74542c3a --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -0,0 +1,56 @@ +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperClickableSteps() { + const { + nextStep, + prevStep, + setStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ setStep(step)} activeStep={activeStep}> + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx new file mode 100644 index 00000000000..d2bb7dc82cb --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-custom-icons.tsx @@ -0,0 +1,57 @@ +import { Calendar, Twitter, User } from "lucide-react" + +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1", icon: }, + { label: "Step 2", icon: }, + { label: "Step 3", icon: }, +] satisfies StepConfig[] + +export default function StepperCustomIcons() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx b/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx new file mode 100644 index 00000000000..1d08a950aeb --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx @@ -0,0 +1,61 @@ +import { CheckCircle2, XCircle } from "lucide-react" + +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperCustomSuccessErrorIcon() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ } + errorIcon={} + > + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx new file mode 100644 index 00000000000..45d87fd65fb --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperDemo() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-descriptions.tsx b/apps/www/registry/new-york/example/stepper-descriptions.tsx new file mode 100644 index 00000000000..1012723467f --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-descriptions.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1", description: "Frist description" }, + { label: "Step 2", description: "Second description" }, + { label: "Step 3", description: "Third description" }, +] satisfies StepConfig[] + +export default function StepperDescriptions() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-label-orientation.tsx b/apps/www/registry/new-york/example/stepper-label-orientation.tsx new file mode 100644 index 00000000000..dce707f12a9 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-label-orientation.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1", description: "Frist description" }, + { label: "Step 2", description: "Second description" }, + { label: "Step 3", description: "Third description" }, +] satisfies StepConfig[] + +export default function StepperLabelOrientation() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx new file mode 100644 index 00000000000..6ae93006ee1 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-optional-steps.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2", optional: true, optionalLabel: "Optional" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperOptionalSteps() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx new file mode 100644 index 00000000000..1f936f17c34 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -0,0 +1,55 @@ +import { Button } from "@/registry/new-york/ui/button" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperOrientation() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + return ( +
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/example/stepper-states.tsx b/apps/www/registry/new-york/example/stepper-states.tsx new file mode 100644 index 00000000000..8a06ad69f23 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-states.tsx @@ -0,0 +1,76 @@ +import { useState } from "react" + +import { Button } from "@/registry/new-york/ui/button" +import { Label } from "@/registry/new-york/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" +import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { useStepper } from "@/registry/new-york/ui/use-stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepConfig[] + +export default function StepperStates() { + const { + nextStep, + prevStep, + resetSteps, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } = useStepper({ + initialStep: 0, + steps, + }) + + const [value, setValue] = useState<"loading" | "error">("loading") + + return ( +
+ setValue(value as "loading" | "error")} + className="mb-4" + > +
+ + +
+
+ + +
+
+ + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} +
+
+ {activeStep === steps.length ? ( + <> +

All steps completed!

+ + + ) : ( + <> + + + + )} +
+
+ ) +} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx new file mode 100644 index 00000000000..6bbe6c1e4ee --- /dev/null +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -0,0 +1,421 @@ +import * as React from "react" +import { VariantProps, cva } from "class-variance-authority" +import { Check, Loader2, X } from "lucide-react" + +import { cn } from "@/lib/utils" + +import { Button } from "./button" +import { Separator } from "./separator" +import { useMediaQuery } from "./use-stepper" + +/********** Context **********/ + +interface StepsContextValue extends StepsProps { + isClickable?: boolean + isError?: boolean + isLoading?: boolean + isVertical?: boolean + isLabelVertical?: boolean + stepCount?: number +} + +const StepsContext = React.createContext({ + activeStep: 0, +}) + +export const useStepperContext = () => React.useContext(StepsContext) + +export const StepsProvider: React.FC<{ + value: StepsContextValue + children: React.ReactNode +}> = ({ value, children }) => { + const isError = value.state === "error" + const isLoading = value.state === "loading" + + const isVertical = value.orientation === "vertical" + const isLabelVertical = + value.orientation !== "vertical" && value.labelOrientation === "vertical" + + return ( + + {children} + + ) +} + +/********** Steps **********/ + +export interface StepsProps { + activeStep: number + orientation?: "vertical" | "horizontal" + state?: "loading" | "error" + responsive?: boolean + onClickStep?: (step: number) => void + successIcon?: React.ReactElement + errorIcon?: React.ReactElement + labelOrientation?: "vertical" | "horizontal" + children?: React.ReactNode + variant?: "default" | "ghost" | "outline" | "secondary" +} + +export const Steps = React.forwardRef( + ( + { + activeStep = 0, + state, + responsive = true, + orientation: orientationProp = "horizontal", + onClickStep, + labelOrientation = "horizontal", + children, + errorIcon, + successIcon, + variant = "default", + }, + ref + ) => { + const childArr = React.Children.toArray(children) + + const stepCount = childArr.length + + const renderHorizontalContent = () => { + if (activeStep <= childArr.length) { + return React.Children.map(childArr[activeStep], (node) => { + if (!React.isValidElement(node)) return + return React.Children.map( + node.props.children, + (childNode) => childNode + ) + }) + } + return null + } + + const isClickable = !!onClickStep + + const isMobile = useMediaQuery("(max-width: 43em)") + + const orientation = isMobile && responsive ? "vertical" : orientationProp + + return ( + +
+ {React.Children.map(children, (child, i) => { + const isCompletedStep = + (React.isValidElement(child) && child.props.isCompletedStep) ?? + i < activeStep + const isLastStep = i === stepCount - 1 + const isCurrentStep = i === activeStep + + const stepProps = { + index: i, + isCompletedStep, + isCurrentStep, + isLastStep, + } + + if (React.isValidElement(child)) { + return React.cloneElement(child, stepProps) + } + + return null + })} +
+ {orientation === "horizontal" && renderHorizontalContent()} +
+ ) + } +) + +Steps.displayName = "Steps" + +/********** Step **********/ + +const stepVariants = cva("relative flex flex-row gap-2", { + variants: { + isLastStep: { + true: "flex-[0_0_auto] justify-end", + false: "flex-[1_0_auto] justify-start", + }, + isVertical: { + true: "flex-col", + false: "items-center", + }, + isClickable: { + true: "cursor-pointer", + }, + }, + compoundVariants: [ + { + isVertical: true, + isLastStep: true, + class: "w-full flex-[1_0_auto] flex-col items-start justify-start", + }, + ], +}) + +export interface StepConfig extends StepLabelProps { + icon?: React.ReactElement +} + +export interface StepProps + extends React.HTMLAttributes, + VariantProps, + StepConfig { + isCompletedStep?: boolean +} + +interface StepStatus { + index: number + isCompletedStep?: boolean + isCurrentStep?: boolean +} + +interface StepAndStatusProps extends StepProps, StepStatus {} + +export const Step = React.forwardRef( + (props, ref) => { + const { + children, + description, + icon: CustomIcon, + index, + isCompletedStep, + isCurrentStep, + isLastStep, + label, + optional, + optionalLabel, + } = props + + const { + isVertical, + isError, + isLoading, + successIcon: CustomSuccessIcon, + errorIcon: CustomErrorIcon, + isLabelVertical, + onClickStep, + isClickable, + variant, + } = useStepperContext() + + const hasVisited = isCurrentStep || isCompletedStep + + const handleClick = (index: number) => { + if (isClickable && onClickStep) { + onClickStep(index) + } + } + + const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon]) + + const Success = React.useMemo( + () => CustomSuccessIcon ?? , + [CustomSuccessIcon] + ) + + const Error = React.useMemo( + () => CustomErrorIcon ?? , + [CustomErrorIcon] + ) + + const RenderIcon = React.useMemo(() => { + if (isCompletedStep) return Success + if (isCurrentStep) { + if (isError) return Error + if (isLoading) return + } + if (Icon) return Icon + return (index || 0) + 1 + }, [ + isCompletedStep, + Success, + isCurrentStep, + Icon, + index, + isError, + Error, + isLoading, + ]) + + return ( +
handleClick(index)} + aria-disabled={!hasVisited} + > +
+ + +
+ + {(isCurrentStep || isCompletedStep) && children} + +
+ ) + } +) + +Step.displayName = "Step" + +/********** StepLabel **********/ + +interface StepLabelProps { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + optionalLabel?: string | React.ReactNode +} + +const StepLabel = ({ + isCurrentStep, + label, + description, + optional, + optionalLabel, +}: StepLabelProps & { + isCurrentStep?: boolean +}) => { + const { isLabelVertical } = useStepperContext() + + const shouldRender = !!label || !!description + + const renderOptionalLabel = !!optional && !!optionalLabel + + return shouldRender ? ( +
+ {!!label && ( +

+ {label} + {renderOptionalLabel && ( + + ({optionalLabel}) + + )} +

+ )} + {!!description && ( +

{description}

+ )} +
+ ) : null +} + +StepLabel.displayName = "StepLabel" + +/********** Connector **********/ + +interface ConnectorProps extends React.HTMLAttributes { + isCompletedStep: boolean + isLastStep?: boolean | null + hasLabel?: boolean + index: number +} + +const Connector = React.memo( + ({ isCompletedStep, children, isLastStep }: ConnectorProps) => { + const { isVertical } = useStepperContext() + + if (isVertical) { + return ( +
+ {!isCompletedStep && ( +
{children}
+ )} +
+ ) + } + + if (isLastStep) { + return null + } + + return ( + + ) + } +) + +Connector.displayName = "Connector" diff --git a/apps/www/registry/new-york/ui/use-stepper.ts b/apps/www/registry/new-york/ui/use-stepper.ts new file mode 100644 index 00000000000..01d2163bcad --- /dev/null +++ b/apps/www/registry/new-york/ui/use-stepper.ts @@ -0,0 +1,124 @@ +import * as React from "react" + +import { StepProps } from "./stepper" + +type useStepper = { + initialStep: number + steps: Pick< + StepProps, + "label" | "description" | "optional" | "optionalLabel" | "icon" + >[] +} + +type useStepperReturn = { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number + isDisabledStep: boolean + isLastStep: boolean + isOptionalStep: boolean | undefined +} + +export function useStepper({ + initialStep, + steps, +}: useStepper): useStepperReturn { + const [activeStep, setActiveStep] = React.useState(initialStep) + + const nextStep = () => { + setActiveStep((prev) => prev + 1) + } + + const prevStep = () => { + setActiveStep((prev) => prev - 1) + } + + const resetSteps = () => { + setActiveStep(initialStep) + } + + const setStep = (step: number) => { + setActiveStep(step) + } + + const isDisabledStep = activeStep === 0 + + const isLastStep = activeStep === steps.length - 1 + + const isOptionalStep = steps[activeStep]?.optional + + return { + nextStep, + prevStep, + resetSteps, + setStep, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } +} + +interface UseMediaQueryOptions { + getInitialValueInEffect: boolean +} + +type MediaQueryCallback = (event: { matches: boolean; media: string }) => void + +/** + * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia + * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent + * */ +function attachMediaListener( + query: MediaQueryList, + callback: MediaQueryCallback +) { + try { + query.addEventListener("change", callback) + return () => query.removeEventListener("change", callback) + } catch (e) { + query.addListener(callback) + return () => query.removeListener(callback) + } +} + +function getInitialValue(query: string, initialValue?: boolean) { + if (typeof initialValue === "boolean") { + return initialValue + } + + if (typeof window !== "undefined" && "matchMedia" in window) { + return window.matchMedia(query).matches + } + + return false +} + +export function useMediaQuery( + query: string, + initialValue?: boolean, + { getInitialValueInEffect }: UseMediaQueryOptions = { + getInitialValueInEffect: true, + } +) { + const [matches, setMatches] = React.useState( + getInitialValueInEffect ? false : getInitialValue(query, initialValue) + ) + const queryRef = React.useRef() + + React.useEffect(() => { + if ("matchMedia" in window) { + queryRef.current = window.matchMedia(query) + setMatches(queryRef.current.matches) + return attachMediaListener(queryRef.current, (event) => + setMatches(event.matches) + ) + } + + return undefined + }, [query]) + + return matches +} diff --git a/apps/www/registry/registry.ts b/apps/www/registry/registry.ts index 60a5582a68e..e01a2aeb983 100644 --- a/apps/www/registry/registry.ts +++ b/apps/www/registry/registry.ts @@ -186,6 +186,11 @@ const ui: Registry = [ dependencies: ["@radix-ui/react-slider"], files: ["ui/slider.tsx"], }, + { + name: "stepper", + type: "components:ui", + files: ["ui/stepper.tsx", "ui/use-stepper.ts"], + }, { name: "switch", type: "components:ui", @@ -647,6 +652,54 @@ const example: Registry = [ registryDependencies: ["slider"], files: ["example/slider-demo.tsx"], }, + { + name: "stepper-demo", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-demo.tsx"], + }, + { + name: "stepper-orientation", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-orientation.tsx"], + }, + { + name: "stepper-description", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-description.tsx"], + }, + { + name: "stepper-custom-icons", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-custom-icons.tsx"], + }, + { + name: "stepper-custom-success-error-icon", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-custom-success-error-icon.tsx"], + }, + { + name: "stepper-label-orientation", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-label-orientation.tsx"], + }, + { + name: "stepper-clickable-steps", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-clickable-steps.tsx"], + }, + { + name: "stepper-optional-steps", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-optional-steps.tsx"], + }, { name: "switch-demo", type: "components:example", From 654f0f41091ab6472f78896a27c0a6dd0f270636 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 10 May 2023 10:46:47 -0300 Subject: [PATCH 04/62] refactor(stepper): update component names --- apps/www/registry/default/ui/stepper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index f1171654f0c..6bbe6c1e4ee 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -291,7 +291,7 @@ export const Step = React.forwardRef( data-clickable={isClickable} disabled={!(hasVisited || isClickable)} className={cn( - "h-12 w-12 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", + "h-10 w-10 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", isCompletedStep || typeof RenderIcon !== "number" ? "px-3 py-2" : "" @@ -392,7 +392,7 @@ const Connector = React.memo(
Date: Tue, 4 Jul 2023 16:40:06 -0300 Subject: [PATCH 05/62] style(stepper): update default size --- apps/www/registry/default/ui/stepper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 64fef7dd786..f1171654f0c 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -291,7 +291,7 @@ export const Step = React.forwardRef( data-clickable={isClickable} disabled={!(hasVisited || isClickable)} className={cn( - "h-10 w-10 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", + "h-12 w-12 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", isCompletedStep || typeof RenderIcon !== "number" ? "px-3 py-2" : "" From f26fd65d8e3c72960252a40771a2580beb51bc2c Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 18 Aug 2023 12:19:42 -0300 Subject: [PATCH 06/62] fix: add aspect square and classname props --- .gitignore | 4 +- apps/www/__registry__/index.tsx | 1241 +++++------------ apps/www/content/docs/components/stepper.mdx | 12 +- apps/www/public/registry/index.json | 8 + .../public/registry/styles/default/card.json | 2 +- .../registry/styles/default/stepper.json | 14 + .../public/registry/styles/new-york/card.json | 2 +- .../registry/styles/new-york/stepper.json | 14 + ...scriptions.tsx => stepper-description.tsx} | 0 apps/www/registry/default/ui/stepper.tsx | 54 +- ...scriptions.tsx => stepper-description.tsx} | 0 apps/www/registry/new-york/ui/stepper.tsx | 54 +- apps/www/registry/registry.ts | 6 + 13 files changed, 510 insertions(+), 901 deletions(-) create mode 100644 apps/www/public/registry/styles/default/stepper.json create mode 100644 apps/www/public/registry/styles/new-york/stepper.json rename apps/www/registry/default/example/{stepper-descriptions.tsx => stepper-description.tsx} (100%) rename apps/www/registry/new-york/example/{stepper-descriptions.tsx => stepper-description.tsx} (100%) diff --git a/.gitignore b/.gitignore index 24c1ac82436..5bd2699c01b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,6 @@ yarn-error.log* .turbo .contentlayer -tsconfig.tsbuildinfo \ No newline at end of file +tsconfig.tsbuildinfo + +.pnpm-store \ No newline at end of file diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 3f4832e677c..505b175c1cf 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -4,15 +4,15 @@ import * as React from "react" export const Index: Record = { - default: { - accordion: { + "default": { + "accordion": { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/accordion")), files: ["registry/default/ui/accordion.tsx"], }, - alert: { + "alert": { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -33,56 +33,56 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/aspect-ratio")), files: ["registry/default/ui/aspect-ratio.tsx"], }, - avatar: { + "avatar": { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/avatar")), files: ["registry/default/ui/avatar.tsx"], }, - badge: { + "badge": { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/badge")), files: ["registry/default/ui/badge.tsx"], }, - button: { + "button": { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/button")), files: ["registry/default/ui/button.tsx"], }, - calendar: { + "calendar": { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/calendar")), files: ["registry/default/ui/calendar.tsx"], }, - card: { + "card": { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/card")), files: ["registry/default/ui/card.tsx"], }, - checkbox: { + "checkbox": { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/checkbox")), files: ["registry/default/ui/checkbox.tsx"], }, - collapsible: { + "collapsible": { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/collapsible")), files: ["registry/default/ui/collapsible.tsx"], }, - command: { + "command": { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -96,7 +96,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/context-menu")), files: ["registry/default/ui/context-menu.tsx"], }, - dialog: { + "dialog": { name: "dialog", type: "components:ui", registryDependencies: undefined, @@ -107,15 +107,13 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/ui/dropdown-menu") - ), + component: React.lazy(() => import("@/registry/default/ui/dropdown-menu")), files: ["registry/default/ui/dropdown-menu.tsx"], }, - form: { + "form": { name: "form", type: "components:ui", - registryDependencies: ["button", "label"], + registryDependencies: ["button","label"], component: React.lazy(() => import("@/registry/default/ui/form")), files: ["registry/default/ui/form.tsx"], }, @@ -126,21 +124,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/hover-card")), files: ["registry/default/ui/hover-card.tsx"], }, - input: { + "input": { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/input")), files: ["registry/default/ui/input.tsx"], }, - label: { + "label": { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/label")), files: ["registry/default/ui/label.tsx"], }, - menubar: { + "menubar": { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -151,19 +149,17 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/ui/navigation-menu") - ), + component: React.lazy(() => import("@/registry/default/ui/navigation-menu")), files: ["registry/default/ui/navigation-menu.tsx"], }, - popover: { + "popover": { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/popover")), files: ["registry/default/ui/popover.tsx"], }, - progress: { + "progress": { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -184,95 +180,91 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/scroll-area")), files: ["registry/default/ui/scroll-area.tsx"], }, - select: { + "select": { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/select")), files: ["registry/default/ui/select.tsx"], }, - separator: { + "separator": { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/separator")), files: ["registry/default/ui/separator.tsx"], }, - sheet: { + "sheet": { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sheet")), files: ["registry/default/ui/sheet.tsx"], }, - skeleton: { + "skeleton": { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/skeleton")), files: ["registry/default/ui/skeleton.tsx"], }, - slider: { + "slider": { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/slider")), files: ["registry/default/ui/slider.tsx"], }, - stepper: { + "stepper": { name: "stepper", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/stepper")), - files: ["registry/default/ui/stepper.tsx"], + files: ["registry/default/ui/stepper.tsx","registry/default/ui/use-stepper.ts"], }, - switch: { + "switch": { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/switch")), files: ["registry/default/ui/switch.tsx"], }, - table: { + "table": { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/table")), files: ["registry/default/ui/table.tsx"], }, - tabs: { + "tabs": { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/tabs")), files: ["registry/default/ui/tabs.tsx"], }, - textarea: { + "textarea": { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/textarea")), files: ["registry/default/ui/textarea.tsx"], }, - toast: { + "toast": { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/toast")), - files: [ - "registry/default/ui/toast.tsx", - "registry/default/ui/use-toast.ts", - "registry/default/ui/toaster.tsx", - ], + files: ["registry/default/ui/toast.tsx","registry/default/ui/use-toast.ts","registry/default/ui/toaster.tsx"], }, - toggle: { + "toggle": { name: "toggle", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/toggle")), files: ["registry/default/ui/toggle.tsx"], }, - tooltip: { + "tooltip": { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -283,1048 +275,816 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy( - () => import("@/registry/default/example/accordion-demo") - ), + component: React.lazy(() => import("@/registry/default/example/accordion-demo")), files: ["registry/default/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/default/example/alert-demo") - ), + component: React.lazy(() => import("@/registry/default/example/alert-demo")), files: ["registry/default/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/default/example/alert-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/alert-destructive")), files: ["registry/default/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog", "button"], - component: React.lazy( - () => import("@/registry/default/example/alert-dialog-demo") - ), + registryDependencies: ["alert-dialog","button"], + component: React.lazy(() => import("@/registry/default/example/alert-dialog-demo")), files: ["registry/default/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy( - () => import("@/registry/default/example/aspect-ratio-demo") - ), + component: React.lazy(() => import("@/registry/default/example/aspect-ratio-demo")), files: ["registry/default/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy( - () => import("@/registry/default/example/avatar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/avatar-demo")), files: ["registry/default/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-demo") - ), + component: React.lazy(() => import("@/registry/default/example/badge-demo")), files: ["registry/default/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/badge-destructive")), files: ["registry/default/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-outline") - ), + component: React.lazy(() => import("@/registry/default/example/badge-outline")), files: ["registry/default/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-secondary") - ), + component: React.lazy(() => import("@/registry/default/example/badge-secondary")), files: ["registry/default/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-demo") - ), + component: React.lazy(() => import("@/registry/default/example/button-demo")), files: ["registry/default/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-secondary") - ), + component: React.lazy(() => import("@/registry/default/example/button-secondary")), files: ["registry/default/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/button-destructive")), files: ["registry/default/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-outline") - ), + component: React.lazy(() => import("@/registry/default/example/button-outline")), files: ["registry/default/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-ghost") - ), + component: React.lazy(() => import("@/registry/default/example/button-ghost")), files: ["registry/default/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-link") - ), + component: React.lazy(() => import("@/registry/default/example/button-link")), files: ["registry/default/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-with-icon") - ), + component: React.lazy(() => import("@/registry/default/example/button-with-icon")), files: ["registry/default/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-loading") - ), + component: React.lazy(() => import("@/registry/default/example/button-loading")), files: ["registry/default/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-icon") - ), + component: React.lazy(() => import("@/registry/default/example/button-icon")), files: ["registry/default/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-as-child") - ), + component: React.lazy(() => import("@/registry/default/example/button-as-child")), files: ["registry/default/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy( - () => import("@/registry/default/example/calendar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/calendar-demo")), files: ["registry/default/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/default/example/calendar-form") - ), + registryDependencies: ["calendar","form","popover"], + component: React.lazy(() => import("@/registry/default/example/calendar-form")), files: ["registry/default/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card", "button", "switch"], - component: React.lazy( - () => import("@/registry/default/example/card-demo") - ), + registryDependencies: ["card","button","switch"], + component: React.lazy(() => import("@/registry/default/example/card-demo")), files: ["registry/default/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button", "card", "input", "label", "select"], - component: React.lazy( - () => import("@/registry/default/example/card-with-form") - ), + registryDependencies: ["button","card","input","label","select"], + component: React.lazy(() => import("@/registry/default/example/card-with-form")), files: ["registry/default/example/card-with-form.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-demo") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-demo")), files: ["registry/default/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-disabled")), files: ["registry/default/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-form-multiple") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/default/example/checkbox-form-multiple")), files: ["registry/default/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-form-single") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/default/example/checkbox-form-single")), files: ["registry/default/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-with-text") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-with-text")), files: ["registry/default/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy( - () => import("@/registry/default/example/collapsible-demo") - ), + component: React.lazy(() => import("@/registry/default/example/collapsible-demo")), files: ["registry/default/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/default/example/combobox-demo") - ), + component: React.lazy(() => import("@/registry/default/example/combobox-demo")), files: ["registry/default/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command", "dropdown-menu", "button"], - component: React.lazy( - () => import("@/registry/default/example/combobox-dropdown-menu") - ), + registryDependencies: ["command","dropdown-menu","button"], + component: React.lazy(() => import("@/registry/default/example/combobox-dropdown-menu")), files: ["registry/default/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command", "form"], - component: React.lazy( - () => import("@/registry/default/example/combobox-form") - ), + registryDependencies: ["command","form"], + component: React.lazy(() => import("@/registry/default/example/combobox-form")), files: ["registry/default/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox", "popover"], - component: React.lazy( - () => import("@/registry/default/example/combobox-popover") - ), + registryDependencies: ["combobox","popover"], + component: React.lazy(() => import("@/registry/default/example/combobox-popover")), files: ["registry/default/example/combobox-popover.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/default/example/command-demo") - ), + component: React.lazy(() => import("@/registry/default/example/command-demo")), files: ["registry/default/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command", "dialog"], - component: React.lazy( - () => import("@/registry/default/example/command-dialog") - ), + registryDependencies: ["command","dialog"], + component: React.lazy(() => import("@/registry/default/example/command-dialog")), files: ["registry/default/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy( - () => import("@/registry/default/example/context-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/context-menu-demo")), files: ["registry/default/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy( - () => import("@/registry/default/example/data-table-demo") - ), + component: React.lazy(() => import("@/registry/default/example/data-table-demo")), files: ["registry/default/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-demo") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-demo")), files: ["registry/default/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button", "calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-form") - ), + registryDependencies: ["button","calendar","form","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-form")), files: ["registry/default/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button", "calendar", "popover", "select"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-with-presets") - ), + registryDependencies: ["button","calendar","popover","select"], + component: React.lazy(() => import("@/registry/default/example/date-picker-with-presets")), files: ["registry/default/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-with-range") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-with-range")), files: ["registry/default/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy( - () => import("@/registry/default/example/dialog-demo") - ), + component: React.lazy(() => import("@/registry/default/example/dialog-demo")), files: ["registry/default/example/dialog-demo.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-demo")), files: ["registry/default/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu", "checkbox"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-checkboxes") - ), + registryDependencies: ["dropdown-menu","checkbox"], + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-checkboxes")), files: ["registry/default/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu", "radio-group"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-radio-group") - ), + registryDependencies: ["dropdown-menu","radio-group"], + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-radio-group")), files: ["registry/default/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy( - () => import("@/registry/default/example/hover-card-demo") - ), + component: React.lazy(() => import("@/registry/default/example/hover-card-demo")), files: ["registry/default/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-demo") - ), + component: React.lazy(() => import("@/registry/default/example/input-demo")), files: ["registry/default/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/input-disabled")), files: ["registry/default/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-file") - ), + component: React.lazy(() => import("@/registry/default/example/input-file")), files: ["registry/default/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input", "button", "form"], - component: React.lazy( - () => import("@/registry/default/example/input-form") - ), + registryDependencies: ["input","button","form"], + component: React.lazy(() => import("@/registry/default/example/input-form")), files: ["registry/default/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input", "button"], - component: React.lazy( - () => import("@/registry/default/example/input-with-button") - ), + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/default/example/input-with-button")), files: ["registry/default/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/default/example/input-with-label") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/default/example/input-with-label")), files: ["registry/default/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/default/example/input-with-text") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/default/example/input-with-text")), files: ["registry/default/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy( - () => import("@/registry/default/example/label-demo") - ), + component: React.lazy(() => import("@/registry/default/example/label-demo")), files: ["registry/default/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy( - () => import("@/registry/default/example/menubar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/menubar-demo")), files: ["registry/default/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy( - () => import("@/registry/default/example/navigation-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/navigation-menu-demo")), files: ["registry/default/example/navigation-menu-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy( - () => import("@/registry/default/example/popover-demo") - ), + component: React.lazy(() => import("@/registry/default/example/popover-demo")), files: ["registry/default/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy( - () => import("@/registry/default/example/progress-demo") - ), + component: React.lazy(() => import("@/registry/default/example/progress-demo")), files: ["registry/default/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy( - () => import("@/registry/default/example/radio-group-demo") - ), + component: React.lazy(() => import("@/registry/default/example/radio-group-demo")), files: ["registry/default/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group", "form"], - component: React.lazy( - () => import("@/registry/default/example/radio-group-form") - ), + registryDependencies: ["radio-group","form"], + component: React.lazy(() => import("@/registry/default/example/radio-group-form")), files: ["registry/default/example/radio-group-form.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/default/example/scroll-area-demo") - ), + component: React.lazy(() => import("@/registry/default/example/scroll-area-demo")), files: ["registry/default/example/scroll-area-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-demo") - ), + component: React.lazy(() => import("@/registry/default/example/select-demo")), files: ["registry/default/example/select-demo.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-form") - ), + component: React.lazy(() => import("@/registry/default/example/select-form")), files: ["registry/default/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy( - () => import("@/registry/default/example/separator-demo") - ), + component: React.lazy(() => import("@/registry/default/example/separator-demo")), files: ["registry/default/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/default/example/sheet-demo") - ), + component: React.lazy(() => import("@/registry/default/example/sheet-demo")), files: ["registry/default/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/default/example/sheet-side") - ), + component: React.lazy(() => import("@/registry/default/example/sheet-side")), files: ["registry/default/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy( - () => import("@/registry/default/example/skeleton-demo") - ), + component: React.lazy(() => import("@/registry/default/example/skeleton-demo")), files: ["registry/default/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy( - () => import("@/registry/default/example/slider-demo") - ), + component: React.lazy(() => import("@/registry/default/example/slider-demo")), files: ["registry/default/example/slider-demo.tsx"], }, "stepper-demo": { name: "stepper-demo", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-demo") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-demo")), files: ["registry/default/example/stepper-demo.tsx"], }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-orientation") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-orientation")), files: ["registry/default/example/stepper-orientation.tsx"], }, - "stepper-descriptions": { - name: "stepper-descriptions", + "stepper-description": { + name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-descriptions") - ), - files: ["registry/default/example/stepper-descriptions.tsx"], + component: React.lazy(() => import("@/registry/default/example/stepper-description")), + files: ["registry/default/example/stepper-description.tsx"], }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-custom-icons") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-custom-icons")), files: ["registry/default/example/stepper-custom-icons.tsx"], }, "stepper-custom-success-error-icon": { name: "stepper-custom-success-error-icon", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => - import("@/registry/default/example/stepper-custom-success-error-icon") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-custom-success-error-icon")), files: ["registry/default/example/stepper-custom-success-error-icon.tsx"], }, "stepper-label-orientation": { name: "stepper-label-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-label-orientation") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-label-orientation")), files: ["registry/default/example/stepper-label-orientation.tsx"], }, - "stepper-states": { - name: "stepper-states", - type: "components:example", - registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-states") - ), - files: ["registry/default/example/stepper-states.tsx"], - }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-clickable-steps") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-clickable-steps")), files: ["registry/default/example/stepper-clickable-steps.tsx"], }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-optional-steps") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-optional-steps")), files: ["registry/default/example/stepper-optional-steps.tsx"], }, + "stepper-states": { + name: "stepper-states", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-states")), + files: ["registry/default/example/stepper-states.tsx"], + }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy( - () => import("@/registry/default/example/switch-demo") - ), + component: React.lazy(() => import("@/registry/default/example/switch-demo")), files: ["registry/default/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch", "form"], - component: React.lazy( - () => import("@/registry/default/example/switch-form") - ), + registryDependencies: ["switch","form"], + component: React.lazy(() => import("@/registry/default/example/switch-form")), files: ["registry/default/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy( - () => import("@/registry/default/example/table-demo") - ), + component: React.lazy(() => import("@/registry/default/example/table-demo")), files: ["registry/default/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy( - () => import("@/registry/default/example/tabs-demo") - ), + component: React.lazy(() => import("@/registry/default/example/tabs-demo")), files: ["registry/default/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/default/example/textarea-demo") - ), + component: React.lazy(() => import("@/registry/default/example/textarea-demo")), files: ["registry/default/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/default/example/textarea-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/textarea-disabled")), files: ["registry/default/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea", "form"], - component: React.lazy( - () => import("@/registry/default/example/textarea-form") - ), + registryDependencies: ["textarea","form"], + component: React.lazy(() => import("@/registry/default/example/textarea-form")), files: ["registry/default/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea", "button"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-button") - ), + registryDependencies: ["textarea","button"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-button")), files: ["registry/default/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-label") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-label")), files: ["registry/default/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-text") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-text")), files: ["registry/default/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toast-demo")), files: ["registry/default/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/toast-destructive")), files: ["registry/default/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-simple") - ), + component: React.lazy(() => import("@/registry/default/example/toast-simple")), files: ["registry/default/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-with-action") - ), + component: React.lazy(() => import("@/registry/default/example/toast-with-action")), files: ["registry/default/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-with-title") - ), + component: React.lazy(() => import("@/registry/default/example/toast-with-title")), files: ["registry/default/example/toast-with-title.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-demo")), files: ["registry/default/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-disabled")), files: ["registry/default/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-lg") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-lg")), files: ["registry/default/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-outline") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-outline")), files: ["registry/default/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-sm") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-sm")), files: ["registry/default/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-with-text") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-with-text")), files: ["registry/default/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy( - () => import("@/registry/default/example/tooltip-demo") - ), + component: React.lazy(() => import("@/registry/default/example/tooltip-demo")), files: ["registry/default/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-blockquote") - ), + component: React.lazy(() => import("@/registry/default/example/typography-blockquote")), files: ["registry/default/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-demo") - ), + component: React.lazy(() => import("@/registry/default/example/typography-demo")), files: ["registry/default/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h1") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h1")), files: ["registry/default/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h2") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h2")), files: ["registry/default/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h3") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h3")), files: ["registry/default/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h4") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h4")), files: ["registry/default/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-inline-code") - ), + component: React.lazy(() => import("@/registry/default/example/typography-inline-code")), files: ["registry/default/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-large") - ), + component: React.lazy(() => import("@/registry/default/example/typography-large")), files: ["registry/default/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-lead") - ), + component: React.lazy(() => import("@/registry/default/example/typography-lead")), files: ["registry/default/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-list") - ), + component: React.lazy(() => import("@/registry/default/example/typography-list")), files: ["registry/default/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-muted") - ), + component: React.lazy(() => import("@/registry/default/example/typography-muted")), files: ["registry/default/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-p") - ), + component: React.lazy(() => import("@/registry/default/example/typography-p")), files: ["registry/default/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-small") - ), + component: React.lazy(() => import("@/registry/default/example/typography-small")), files: ["registry/default/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-table") - ), + component: React.lazy(() => import("@/registry/default/example/typography-table")), files: ["registry/default/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/mode-toggle") - ), + component: React.lazy(() => import("@/registry/default/example/mode-toggle")), files: ["registry/default/example/mode-toggle.tsx"], }, - }, - "new-york": { - accordion: { + }, "new-york": { + "accordion": { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/accordion")), files: ["registry/new-york/ui/accordion.tsx"], }, - alert: { + "alert": { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -1335,70 +1095,66 @@ export const Index: Record = { name: "alert-dialog", type: "components:ui", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/ui/alert-dialog") - ), + component: React.lazy(() => import("@/registry/new-york/ui/alert-dialog")), files: ["registry/new-york/ui/alert-dialog.tsx"], }, "aspect-ratio": { name: "aspect-ratio", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/aspect-ratio") - ), + component: React.lazy(() => import("@/registry/new-york/ui/aspect-ratio")), files: ["registry/new-york/ui/aspect-ratio.tsx"], }, - avatar: { + "avatar": { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/avatar")), files: ["registry/new-york/ui/avatar.tsx"], }, - badge: { + "badge": { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/badge")), files: ["registry/new-york/ui/badge.tsx"], }, - button: { + "button": { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/button")), files: ["registry/new-york/ui/button.tsx"], }, - calendar: { + "calendar": { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/calendar")), files: ["registry/new-york/ui/calendar.tsx"], }, - card: { + "card": { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/card")), files: ["registry/new-york/ui/card.tsx"], }, - checkbox: { + "checkbox": { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/checkbox")), files: ["registry/new-york/ui/checkbox.tsx"], }, - collapsible: { + "collapsible": { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/collapsible")), files: ["registry/new-york/ui/collapsible.tsx"], }, - command: { + "command": { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -1409,12 +1165,10 @@ export const Index: Record = { name: "context-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/context-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/context-menu")), files: ["registry/new-york/ui/context-menu.tsx"], }, - dialog: { + "dialog": { name: "dialog", type: "components:ui", registryDependencies: undefined, @@ -1425,15 +1179,13 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/dropdown-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/dropdown-menu")), files: ["registry/new-york/ui/dropdown-menu.tsx"], }, - form: { + "form": { name: "form", type: "components:ui", - registryDependencies: ["button", "label"], + registryDependencies: ["button","label"], component: React.lazy(() => import("@/registry/new-york/ui/form")), files: ["registry/new-york/ui/form.tsx"], }, @@ -1444,21 +1196,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/hover-card")), files: ["registry/new-york/ui/hover-card.tsx"], }, - input: { + "input": { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/input")), files: ["registry/new-york/ui/input.tsx"], }, - label: { + "label": { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/label")), files: ["registry/new-york/ui/label.tsx"], }, - menubar: { + "menubar": { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -1469,19 +1221,17 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/navigation-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/navigation-menu")), files: ["registry/new-york/ui/navigation-menu.tsx"], }, - popover: { + "popover": { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/popover")), files: ["registry/new-york/ui/popover.tsx"], }, - progress: { + "progress": { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -1502,95 +1252,91 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/scroll-area")), files: ["registry/new-york/ui/scroll-area.tsx"], }, - select: { + "select": { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/select")), files: ["registry/new-york/ui/select.tsx"], }, - separator: { + "separator": { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/separator")), files: ["registry/new-york/ui/separator.tsx"], }, - sheet: { + "sheet": { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sheet")), files: ["registry/new-york/ui/sheet.tsx"], }, - skeleton: { + "skeleton": { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/skeleton")), files: ["registry/new-york/ui/skeleton.tsx"], }, - slider: { + "slider": { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/slider")), files: ["registry/new-york/ui/slider.tsx"], }, - stepper: { + "stepper": { name: "stepper", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/stepper")), - files: ["registry/new-york/ui/stepper.tsx"], + files: ["registry/new-york/ui/stepper.tsx","registry/new-york/ui/use-stepper.ts"], }, - switch: { + "switch": { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/switch")), files: ["registry/new-york/ui/switch.tsx"], }, - table: { + "table": { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/table")), files: ["registry/new-york/ui/table.tsx"], }, - tabs: { + "tabs": { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/tabs")), files: ["registry/new-york/ui/tabs.tsx"], }, - textarea: { + "textarea": { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/textarea")), files: ["registry/new-york/ui/textarea.tsx"], }, - toast: { + "toast": { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/toast")), - files: [ - "registry/new-york/ui/toast.tsx", - "registry/new-york/ui/use-toast.ts", - "registry/new-york/ui/toaster.tsx", - ], + files: ["registry/new-york/ui/toast.tsx","registry/new-york/ui/use-toast.ts","registry/new-york/ui/toaster.tsx"], }, - toggle: { + "toggle": { name: "toggle", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/toggle")), files: ["registry/new-york/ui/toggle.tsx"], }, - tooltip: { + "tooltip": { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -1601,1040 +1347,805 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy( - () => import("@/registry/new-york/example/accordion-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/accordion-demo")), files: ["registry/new-york/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/alert-demo")), files: ["registry/new-york/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/alert-destructive")), files: ["registry/new-york/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-dialog-demo") - ), + registryDependencies: ["alert-dialog","button"], + component: React.lazy(() => import("@/registry/new-york/example/alert-dialog-demo")), files: ["registry/new-york/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy( - () => import("@/registry/new-york/example/aspect-ratio-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/aspect-ratio-demo")), files: ["registry/new-york/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy( - () => import("@/registry/new-york/example/avatar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/avatar-demo")), files: ["registry/new-york/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-demo")), files: ["registry/new-york/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-destructive")), files: ["registry/new-york/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-outline")), files: ["registry/new-york/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-secondary") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-secondary")), files: ["registry/new-york/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-demo")), files: ["registry/new-york/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-secondary") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-secondary")), files: ["registry/new-york/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-destructive")), files: ["registry/new-york/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-outline")), files: ["registry/new-york/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-ghost") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-ghost")), files: ["registry/new-york/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-link") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-link")), files: ["registry/new-york/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-with-icon") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-with-icon")), files: ["registry/new-york/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-loading") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-loading")), files: ["registry/new-york/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-icon") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-icon")), files: ["registry/new-york/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-as-child") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-as-child")), files: ["registry/new-york/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy( - () => import("@/registry/new-york/example/calendar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/calendar-demo")), files: ["registry/new-york/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/calendar-form") - ), + registryDependencies: ["calendar","form","popover"], + component: React.lazy(() => import("@/registry/new-york/example/calendar-form")), files: ["registry/new-york/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card", "button", "switch"], - component: React.lazy( - () => import("@/registry/new-york/example/card-demo") - ), + registryDependencies: ["card","button","switch"], + component: React.lazy(() => import("@/registry/new-york/example/card-demo")), files: ["registry/new-york/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button", "card", "input", "label", "select"], - component: React.lazy( - () => import("@/registry/new-york/example/card-with-form") - ), + registryDependencies: ["button","card","input","label","select"], + component: React.lazy(() => import("@/registry/new-york/example/card-with-form")), files: ["registry/new-york/example/card-with-form.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-demo")), files: ["registry/new-york/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-disabled")), files: ["registry/new-york/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-form-multiple") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-multiple")), files: ["registry/new-york/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-form-single") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-single")), files: ["registry/new-york/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-with-text") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-with-text")), files: ["registry/new-york/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy( - () => import("@/registry/new-york/example/collapsible-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/collapsible-demo")), files: ["registry/new-york/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/combobox-demo")), files: ["registry/new-york/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command", "dropdown-menu", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-dropdown-menu") - ), + registryDependencies: ["command","dropdown-menu","button"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-dropdown-menu")), files: ["registry/new-york/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-form") - ), + registryDependencies: ["command","form"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-form")), files: ["registry/new-york/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-popover") - ), + registryDependencies: ["combobox","popover"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-popover")), files: ["registry/new-york/example/combobox-popover.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/new-york/example/command-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/command-demo")), files: ["registry/new-york/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command", "dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/command-dialog") - ), + registryDependencies: ["command","dialog"], + component: React.lazy(() => import("@/registry/new-york/example/command-dialog")), files: ["registry/new-york/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/context-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/context-menu-demo")), files: ["registry/new-york/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy( - () => import("@/registry/new-york/example/data-table-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/data-table-demo")), files: ["registry/new-york/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-demo") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-demo")), files: ["registry/new-york/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button", "calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-form") - ), + registryDependencies: ["button","calendar","form","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-form")), files: ["registry/new-york/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button", "calendar", "popover", "select"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-with-presets") - ), + registryDependencies: ["button","calendar","popover","select"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-presets")), files: ["registry/new-york/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-with-range") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-range")), files: ["registry/new-york/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/dialog-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/dialog-demo")), files: ["registry/new-york/example/dialog-demo.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-demo")), files: ["registry/new-york/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu", "checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-checkboxes") - ), + registryDependencies: ["dropdown-menu","checkbox"], + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-checkboxes")), files: ["registry/new-york/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu", "radio-group"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-radio-group") - ), + registryDependencies: ["dropdown-menu","radio-group"], + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-radio-group")), files: ["registry/new-york/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy( - () => import("@/registry/new-york/example/hover-card-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/hover-card-demo")), files: ["registry/new-york/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-demo")), files: ["registry/new-york/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-disabled")), files: ["registry/new-york/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-file") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-file")), files: ["registry/new-york/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input", "button", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/input-form") - ), + registryDependencies: ["input","button","form"], + component: React.lazy(() => import("@/registry/new-york/example/input-form")), files: ["registry/new-york/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-button") - ), + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-button")), files: ["registry/new-york/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-label") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-label")), files: ["registry/new-york/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-text") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-text")), files: ["registry/new-york/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy( - () => import("@/registry/new-york/example/label-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/label-demo")), files: ["registry/new-york/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy( - () => import("@/registry/new-york/example/menubar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/menubar-demo")), files: ["registry/new-york/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/navigation-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/navigation-menu-demo")), files: ["registry/new-york/example/navigation-menu-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy( - () => import("@/registry/new-york/example/popover-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/popover-demo")), files: ["registry/new-york/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy( - () => import("@/registry/new-york/example/progress-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/progress-demo")), files: ["registry/new-york/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy( - () => import("@/registry/new-york/example/radio-group-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/radio-group-demo")), files: ["registry/new-york/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/radio-group-form") - ), + registryDependencies: ["radio-group","form"], + component: React.lazy(() => import("@/registry/new-york/example/radio-group-form")), files: ["registry/new-york/example/radio-group-form.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/new-york/example/scroll-area-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/scroll-area-demo")), files: ["registry/new-york/example/scroll-area-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-demo")), files: ["registry/new-york/example/select-demo.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-form") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-form")), files: ["registry/new-york/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy( - () => import("@/registry/new-york/example/separator-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/separator-demo")), files: ["registry/new-york/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/new-york/example/sheet-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/sheet-demo")), files: ["registry/new-york/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/new-york/example/sheet-side") - ), + component: React.lazy(() => import("@/registry/new-york/example/sheet-side")), files: ["registry/new-york/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy( - () => import("@/registry/new-york/example/skeleton-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/skeleton-demo")), files: ["registry/new-york/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy( - () => import("@/registry/new-york/example/slider-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/slider-demo")), files: ["registry/new-york/example/slider-demo.tsx"], }, "stepper-demo": { name: "stepper-demo", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-demo")), files: ["registry/new-york/example/stepper-demo.tsx"], }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-orientation") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-orientation")), files: ["registry/new-york/example/stepper-orientation.tsx"], }, - "stepper-descriptions": { - name: "stepper-descriptions", + "stepper-description": { + name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-descriptions") - ), - files: ["registry/new-york/example/stepper-descriptions.tsx"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-description")), + files: ["registry/new-york/example/stepper-description.tsx"], }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-custom-icons") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-icons")), files: ["registry/new-york/example/stepper-custom-icons.tsx"], }, "stepper-custom-success-error-icon": { name: "stepper-custom-success-error-icon", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => - import( - "@/registry/new-york/example/stepper-custom-success-error-icon" - ) - ), - files: [ - "registry/new-york/example/stepper-custom-success-error-icon.tsx", - ], + component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-success-error-icon")), + files: ["registry/new-york/example/stepper-custom-success-error-icon.tsx"], }, "stepper-label-orientation": { name: "stepper-label-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-label-orientation") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-label-orientation")), files: ["registry/new-york/example/stepper-label-orientation.tsx"], }, - "stepper-states": { - name: "stepper-states", - type: "components:example", - registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-states") - ), - files: ["registry/new-york/example/stepper-states.tsx"], - }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-clickable-steps") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-clickable-steps")), files: ["registry/new-york/example/stepper-clickable-steps.tsx"], }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-optional-steps") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-optional-steps")), files: ["registry/new-york/example/stepper-optional-steps.tsx"], }, + "stepper-states": { + name: "stepper-states", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-states")), + files: ["registry/new-york/example/stepper-states.tsx"], + }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy( - () => import("@/registry/new-york/example/switch-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/switch-demo")), files: ["registry/new-york/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/switch-form") - ), + registryDependencies: ["switch","form"], + component: React.lazy(() => import("@/registry/new-york/example/switch-form")), files: ["registry/new-york/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy( - () => import("@/registry/new-york/example/table-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/table-demo")), files: ["registry/new-york/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy( - () => import("@/registry/new-york/example/tabs-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/tabs-demo")), files: ["registry/new-york/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/textarea-demo")), files: ["registry/new-york/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/textarea-disabled")), files: ["registry/new-york/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-form") - ), + registryDependencies: ["textarea","form"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-form")), files: ["registry/new-york/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-button") - ), + registryDependencies: ["textarea","button"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-button")), files: ["registry/new-york/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-label") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-label")), files: ["registry/new-york/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-text") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-text")), files: ["registry/new-york/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-demo")), files: ["registry/new-york/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-destructive")), files: ["registry/new-york/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-simple") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-simple")), files: ["registry/new-york/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-with-action") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-with-action")), files: ["registry/new-york/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-with-title") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-with-title")), files: ["registry/new-york/example/toast-with-title.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-demo")), files: ["registry/new-york/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-disabled")), files: ["registry/new-york/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-lg") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-lg")), files: ["registry/new-york/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-outline")), files: ["registry/new-york/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-sm") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-sm")), files: ["registry/new-york/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-with-text") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-with-text")), files: ["registry/new-york/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy( - () => import("@/registry/new-york/example/tooltip-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/tooltip-demo")), files: ["registry/new-york/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-blockquote") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-blockquote")), files: ["registry/new-york/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-demo")), files: ["registry/new-york/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h1") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h1")), files: ["registry/new-york/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h2") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h2")), files: ["registry/new-york/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h3") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h3")), files: ["registry/new-york/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h4") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h4")), files: ["registry/new-york/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-inline-code") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-inline-code")), files: ["registry/new-york/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-large") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-large")), files: ["registry/new-york/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-lead") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-lead")), files: ["registry/new-york/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-list") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-list")), files: ["registry/new-york/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-muted") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-muted")), files: ["registry/new-york/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-p") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-p")), files: ["registry/new-york/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-small") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-small")), files: ["registry/new-york/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-table") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-table")), files: ["registry/new-york/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/mode-toggle") - ), + component: React.lazy(() => import("@/registry/new-york/example/mode-toggle")), files: ["registry/new-york/example/mode-toggle.tsx"], }, }, diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index ec82ceb446d..052e0dc6c48 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -140,7 +140,7 @@ export const StepperDemo = () => { ### `` ```tsx -interface StepsProps { +interface StepsProps extends React.HTMLAttributes { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -181,7 +181,13 @@ interface StepStatus { isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus {} +interface StepAndStatusProps extends StepProps, StepStatus { + additionalClassName?: { + button?: string + label?: string + description?: string + } +} ``` ### `useStepper` @@ -228,7 +234,7 @@ We can pass the `orientation` prop to the Steps component to set the orientation The Step component also accepts a `description` prop which can be used to provide some extra information about the step. - + ### Custom Icons diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index 78d8c9058c3..1c8d7086f8c 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -302,6 +302,14 @@ ], "type": "components:ui" }, + { + "name": "stepper", + "files": [ + "ui/stepper.tsx", + "ui/use-stepper.ts" + ], + "type": "components:ui" + }, { "name": "switch", "dependencies": [ diff --git a/apps/www/public/registry/styles/default/card.json b/apps/www/public/registry/styles/default/card.json index a039fbc8042..ef31d3a699f 100644 --- a/apps/www/public/registry/styles/default/card.json +++ b/apps/www/public/registry/styles/default/card.json @@ -3,7 +3,7 @@ "files": [ { "name": "card.tsx", - "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n" + "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json new file mode 100644 index 00000000000..9df984a3f62 --- /dev/null +++ b/apps/www/public/registry/styles/default/stepper.json @@ -0,0 +1,14 @@ +{ + "name": "stepper", + "files": [ + { + "name": "stepper.tsx", + "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** Context **********/\n\ninterface StepsContextValue extends StepsProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepsContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepsContext)\n\nexport const StepsProvider: React.FC<{\n value: StepsContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Steps **********/\n\nexport interface StepsProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Steps = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nSteps.displayName = \"Steps\"\n\n/********** Step **********/\n\nconst stepVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepConfig extends StepLabelProps {\n icon?: React.ReactElement\n}\n\nexport interface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\ninterface StepAndStatusProps extends StepProps, StepStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const Step = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStep.displayName = \"Step\"\n\n/********** StepLabel **********/\n\ninterface StepLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\n\nconst StepLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n

{description}

\n )}\n
\n ) : null\n}\n\nStepLabel.displayName = \"StepLabel\"\n\n/********** Connector **********/\n\ninterface ConnectorProps extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst Connector = React.memo(\n ({ isCompletedStep, children, isLastStep }: ConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nConnector.displayName = \"Connector\"\n" + }, + { + "name": "use-stepper.ts", + "content": "import * as React from \"react\"\n\nimport { StepProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" + } + ], + "type": "components:ui" +} \ No newline at end of file diff --git a/apps/www/public/registry/styles/new-york/card.json b/apps/www/public/registry/styles/new-york/card.json index 99405269f7d..af7ee0e12e5 100644 --- a/apps/www/public/registry/styles/new-york/card.json +++ b/apps/www/public/registry/styles/new-york/card.json @@ -3,7 +3,7 @@ "files": [ { "name": "card.tsx", - "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n" + "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json new file mode 100644 index 00000000000..e10aaefb48a --- /dev/null +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -0,0 +1,14 @@ +{ + "name": "stepper", + "files": [ + { + "name": "stepper.tsx", + "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** Context **********/\n\ninterface StepsContextValue extends StepsProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepsContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepsContext)\n\nexport const StepsProvider: React.FC<{\n value: StepsContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Steps **********/\n\nexport interface StepsProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Steps = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nSteps.displayName = \"Steps\"\n\n/********** Step **********/\n\nconst stepVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepConfig extends StepLabelProps {\n icon?: React.ReactElement\n}\n\nexport interface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\ninterface StepAndStatusProps extends StepProps, StepStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const Step = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStep.displayName = \"Step\"\n\n/********** StepLabel **********/\n\ninterface StepLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\n\nconst StepLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n

{description}

\n )}\n
\n ) : null\n}\n\nStepLabel.displayName = \"StepLabel\"\n\n/********** Connector **********/\n\ninterface ConnectorProps extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst Connector = React.memo(\n ({ isCompletedStep, children, isLastStep }: ConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nConnector.displayName = \"Connector\"\n" + }, + { + "name": "use-stepper.ts", + "content": "import * as React from \"react\"\n\nimport { StepProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" + } + ], + "type": "components:ui" +} \ No newline at end of file diff --git a/apps/www/registry/default/example/stepper-descriptions.tsx b/apps/www/registry/default/example/stepper-description.tsx similarity index 100% rename from apps/www/registry/default/example/stepper-descriptions.tsx rename to apps/www/registry/default/example/stepper-description.tsx diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index f1171654f0c..b9c8a34da41 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -53,7 +53,7 @@ export const StepsProvider: React.FC<{ /********** Steps **********/ -export interface StepsProps { +export interface StepsProps extends React.HTMLAttributes { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -79,6 +79,8 @@ export const Steps = React.forwardRef( errorIcon, successIcon, variant = "default", + className, + ...props }, ref ) => { @@ -122,10 +124,12 @@ export const Steps = React.forwardRef( }} >
{React.Children.map(children, (child, i) => { @@ -188,8 +192,8 @@ export interface StepConfig extends StepLabelProps { export interface StepProps extends React.HTMLAttributes, - VariantProps, - StepConfig { + VariantProps, + StepConfig { isCompletedStep?: boolean } @@ -199,7 +203,13 @@ interface StepStatus { isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus {} +interface StepAndStatusProps extends StepProps, StepStatus { + additionalClassName?: { + button?: string + label?: string + description?: string + } +} export const Step = React.forwardRef( (props, ref) => { @@ -214,6 +224,9 @@ export const Step = React.forwardRef( label, optional, optionalLabel, + className, + additionalClassName, + ...rest } = props const { @@ -269,11 +282,15 @@ export const Step = React.forwardRef( return (
handleClick(index)} aria-disabled={!hasVisited} @@ -289,12 +306,13 @@ export const Step = React.forwardRef( data-invalid={isCurrentStep && isError} data-highlighted={isCompletedStep} data-clickable={isClickable} - disabled={!(hasVisited || isClickable)} + disabled={!hasVisited} className={cn( - "h-12 w-12 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", + "aspect-square h-12 w-12 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", isCompletedStep || typeof RenderIcon !== "number" ? "px-3 py-2" - : "" + : "", + additionalClassName?.button )} variant={isCurrentStep && isError ? "destructive" : variant} > @@ -305,6 +323,8 @@ export const Step = React.forwardRef( description={description} optional={optional} optionalLabel={optionalLabel} + labelClassName={additionalClassName?.label} + descriptionClassName={additionalClassName?.description} {...{ isCurrentStep }} />
@@ -330,6 +350,8 @@ interface StepLabelProps { description?: string | React.ReactNode optional?: boolean optionalLabel?: string | React.ReactNode + labelClassName?: string + descriptionClassName?: string } const StepLabel = ({ @@ -338,6 +360,8 @@ const StepLabel = ({ description, optional, optionalLabel, + labelClassName, + descriptionClassName, }: StepLabelProps & { isCurrentStep?: boolean }) => { @@ -356,7 +380,7 @@ const StepLabel = ({ )} > {!!label && ( -

+

{label} {renderOptionalLabel && ( @@ -366,7 +390,7 @@ const StepLabel = ({

)} {!!description && ( -

{description}

+

{description}

)}
) : null diff --git a/apps/www/registry/new-york/example/stepper-descriptions.tsx b/apps/www/registry/new-york/example/stepper-description.tsx similarity index 100% rename from apps/www/registry/new-york/example/stepper-descriptions.tsx rename to apps/www/registry/new-york/example/stepper-description.tsx diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 6bbe6c1e4ee..1d7c44fa3f8 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -53,7 +53,7 @@ export const StepsProvider: React.FC<{ /********** Steps **********/ -export interface StepsProps { +export interface StepsProps extends React.HTMLAttributes { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -79,6 +79,8 @@ export const Steps = React.forwardRef( errorIcon, successIcon, variant = "default", + className, + ...props }, ref ) => { @@ -122,10 +124,12 @@ export const Steps = React.forwardRef( }} >
{React.Children.map(children, (child, i) => { @@ -188,8 +192,8 @@ export interface StepConfig extends StepLabelProps { export interface StepProps extends React.HTMLAttributes, - VariantProps, - StepConfig { + VariantProps, + StepConfig { isCompletedStep?: boolean } @@ -199,7 +203,13 @@ interface StepStatus { isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus {} +interface StepAndStatusProps extends StepProps, StepStatus { + additionalClassName?: { + button?: string + label?: string + description?: string + } +} export const Step = React.forwardRef( (props, ref) => { @@ -214,6 +224,9 @@ export const Step = React.forwardRef( label, optional, optionalLabel, + className, + additionalClassName, + ...rest } = props const { @@ -269,11 +282,15 @@ export const Step = React.forwardRef( return (
handleClick(index)} aria-disabled={!hasVisited} @@ -289,12 +306,13 @@ export const Step = React.forwardRef( data-invalid={isCurrentStep && isError} data-highlighted={isCompletedStep} data-clickable={isClickable} - disabled={!(hasVisited || isClickable)} + disabled={!hasVisited} className={cn( - "h-10 w-10 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", + "aspect-square h-10 w-10 rounded-full data-[highlighted=true]:bg-green-700 data-[highlighted=true]:text-white", isCompletedStep || typeof RenderIcon !== "number" ? "px-3 py-2" - : "" + : "", + additionalClassName?.button )} variant={isCurrentStep && isError ? "destructive" : variant} > @@ -305,6 +323,8 @@ export const Step = React.forwardRef( description={description} optional={optional} optionalLabel={optionalLabel} + labelClassName={additionalClassName?.label} + descriptionClassName={additionalClassName?.description} {...{ isCurrentStep }} />
@@ -330,6 +350,8 @@ interface StepLabelProps { description?: string | React.ReactNode optional?: boolean optionalLabel?: string | React.ReactNode + labelClassName?: string + descriptionClassName?: string } const StepLabel = ({ @@ -338,6 +360,8 @@ const StepLabel = ({ description, optional, optionalLabel, + labelClassName, + descriptionClassName, }: StepLabelProps & { isCurrentStep?: boolean }) => { @@ -356,7 +380,7 @@ const StepLabel = ({ )} > {!!label && ( -

+

{label} {renderOptionalLabel && ( @@ -366,7 +390,7 @@ const StepLabel = ({

)} {!!description && ( -

{description}

+

{description}

)}
) : null diff --git a/apps/www/registry/registry.ts b/apps/www/registry/registry.ts index e01a2aeb983..80778c5b98d 100644 --- a/apps/www/registry/registry.ts +++ b/apps/www/registry/registry.ts @@ -700,6 +700,12 @@ const example: Registry = [ registryDependencies: ["stepper"], files: ["example/stepper-optional-steps.tsx"], }, + { + name: "stepper-states", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-states.tsx"], + }, { name: "switch-demo", type: "components:example", From 21b26a099874a8f4746f87044d2d9e43e1f9a93b Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 18 Dec 2023 00:26:59 -0300 Subject: [PATCH 07/62] chore: rename components --- .../example/stepper-clickable-steps.tsx | 16 ++-- .../default/example/stepper-custom-icons.tsx | 16 ++-- .../stepper-custom-success-error-icon.tsx | 16 ++-- .../registry/default/example/stepper-demo.tsx | 16 ++-- .../default/example/stepper-description.tsx | 16 ++-- .../example/stepper-label-orientation.tsx | 16 ++-- .../example/stepper-optional-steps.tsx | 16 ++-- .../default/example/stepper-orientation.tsx | 16 ++-- .../default/example/stepper-states.tsx | 16 ++-- apps/www/registry/default/ui/stepper.tsx | 83 +++++++++--------- apps/www/registry/default/ui/use-stepper.ts | 4 +- .../example/stepper-clickable-steps.tsx | 16 ++-- .../new-york/example/stepper-custom-icons.tsx | 16 ++-- .../stepper-custom-success-error-icon.tsx | 16 ++-- .../new-york/example/stepper-demo.tsx | 16 ++-- .../new-york/example/stepper-description.tsx | 16 ++-- .../example/stepper-label-orientation.tsx | 16 ++-- .../example/stepper-optional-steps.tsx | 16 ++-- .../new-york/example/stepper-orientation.tsx | 16 ++-- .../new-york/example/stepper-states.tsx | 16 ++-- apps/www/registry/new-york/ui/stepper.tsx | 84 ++++++++++--------- apps/www/registry/new-york/ui/use-stepper.ts | 4 +- 22 files changed, 272 insertions(+), 191 deletions(-) diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index 304c9bc8143..e4a1e236008 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperClickableSteps() { const { @@ -25,15 +29,15 @@ export default function StepperClickableSteps() { return (
- setStep(step)} activeStep={activeStep}> + setStep(step)} activeStep={activeStep}> {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx index c3f6e0cfb78..96ab9fccd55 100644 --- a/apps/www/registry/default/example/stepper-custom-icons.tsx +++ b/apps/www/registry/default/example/stepper-custom-icons.tsx @@ -1,14 +1,18 @@ import { Calendar, Twitter, User } from "lucide-react" import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1", icon: }, { label: "Step 2", icon: }, { label: "Step 3", icon: }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperCustomIcons() { const { @@ -26,15 +30,15 @@ export default function StepperCustomIcons() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx b/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx index 829bb821aac..735507b3195 100644 --- a/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx +++ b/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx @@ -1,14 +1,18 @@ import { CheckCircle2, XCircle } from "lucide-react" import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperCustomSuccessErrorIcon() { const { @@ -26,19 +30,19 @@ export default function StepperCustomSuccessErrorIcon() { return (
- } errorIcon={} > {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx index e37129d9fe7..1265da9aa62 100644 --- a/apps/www/registry/default/example/stepper-demo.tsx +++ b/apps/www/registry/default/example/stepper-demo.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperDemo() { const { @@ -24,15 +28,15 @@ export default function StepperDemo() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-description.tsx b/apps/www/registry/default/example/stepper-description.tsx index e84ec084a79..4eb24122c76 100644 --- a/apps/www/registry/default/example/stepper-description.tsx +++ b/apps/www/registry/default/example/stepper-description.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1", description: "Frist description" }, { label: "Step 2", description: "Second description" }, { label: "Step 3", description: "Third description" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperDescriptions() { const { @@ -24,15 +28,15 @@ export default function StepperDescriptions() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-label-orientation.tsx b/apps/www/registry/default/example/stepper-label-orientation.tsx index da8789b6a55..979815e9d08 100644 --- a/apps/www/registry/default/example/stepper-label-orientation.tsx +++ b/apps/www/registry/default/example/stepper-label-orientation.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1", description: "Frist description" }, { label: "Step 2", description: "Second description" }, { label: "Step 3", description: "Third description" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperLabelOrientation() { const { @@ -24,15 +28,15 @@ export default function StepperLabelOrientation() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx index 572837fa7ea..fa2a34fe71c 100644 --- a/apps/www/registry/default/example/stepper-optional-steps.tsx +++ b/apps/www/registry/default/example/stepper-optional-steps.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2", optional: true, optionalLabel: "Optional" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperOptionalSteps() { const { @@ -24,15 +28,15 @@ export default function StepperOptionalSteps() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index ce21fc03a9e..d5229b305e5 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/default/ui/button" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperOrientation() { const { @@ -24,15 +28,15 @@ export default function StepperOrientation() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/example/stepper-states.tsx b/apps/www/registry/default/example/stepper-states.tsx index 12d43939781..acf8370eec4 100644 --- a/apps/www/registry/default/example/stepper-states.tsx +++ b/apps/www/registry/default/example/stepper-states.tsx @@ -3,14 +3,18 @@ import { useState } from "react" import { Button } from "@/registry/default/ui/button" import { Label } from "@/registry/default/ui/label" import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" -import { Step, StepConfig, Steps } from "@/registry/default/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/default/ui/stepper" import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperStates() { const { @@ -45,15 +49,15 @@ export default function StepperStates() {
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index b9c8a34da41..08f58208932 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -8,9 +8,9 @@ import { Button } from "./button" import { Separator } from "./separator" import { useMediaQuery } from "./use-stepper" -/********** Context **********/ +/********** StepperContext **********/ -interface StepsContextValue extends StepsProps { +interface StepperContextValue extends StepperProps { isClickable?: boolean isError?: boolean isLoading?: boolean @@ -19,14 +19,14 @@ interface StepsContextValue extends StepsProps { stepCount?: number } -const StepsContext = React.createContext({ +const StepperContext = React.createContext({ activeStep: 0, }) -export const useStepperContext = () => React.useContext(StepsContext) +export const useStepperContext = () => React.useContext(StepperContext) -export const StepsProvider: React.FC<{ - value: StepsContextValue +export const StepperProvider: React.FC<{ + value: StepperContextValue children: React.ReactNode }> = ({ value, children }) => { const isError = value.state === "error" @@ -37,7 +37,7 @@ export const StepsProvider: React.FC<{ value.orientation !== "vertical" && value.labelOrientation === "vertical" return ( - {children} - + ) } -/********** Steps **********/ +/********** Stepper **********/ -export interface StepsProps extends React.HTMLAttributes { +export interface StepperProps extends React.HTMLAttributes { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -66,7 +66,7 @@ export interface StepsProps extends React.HTMLAttributes { variant?: "default" | "ghost" | "outline" | "secondary" } -export const Steps = React.forwardRef( +export const Stepper = React.forwardRef( ( { activeStep = 0, @@ -108,7 +108,7 @@ export const Steps = React.forwardRef( const orientation = isMobile && responsive ? "vertical" : orientationProp return ( - ( })}
{orientation === "horizontal" && renderHorizontalContent()} - + ) } ) -Steps.displayName = "Steps" +Stepper.displayName = "Stepper" -/********** Step **********/ +/********** StepperItem **********/ -const stepVariants = cva("relative flex flex-row gap-2", { +const stepperItemVariants = cva("relative flex flex-row gap-2", { variants: { isLastStep: { true: "flex-[0_0_auto] justify-end", @@ -186,24 +186,24 @@ const stepVariants = cva("relative flex flex-row gap-2", { ], }) -export interface StepConfig extends StepLabelProps { +export interface StepperConfig extends StepperItemLabelProps { icon?: React.ReactElement } -export interface StepProps +interface StepProps extends React.HTMLAttributes, - VariantProps, - StepConfig { + VariantProps, + StepperConfig { isCompletedStep?: boolean } -interface StepStatus { +interface StepperItemStatus { index: number isCompletedStep?: boolean isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus { +export interface StepperItemProps extends StepProps, StepperItemStatus { additionalClassName?: { button?: string label?: string @@ -211,7 +211,7 @@ interface StepAndStatusProps extends StepProps, StepStatus { } } -export const Step = React.forwardRef( +export const StepperItem = React.forwardRef( (props, ref) => { const { children, @@ -284,7 +284,7 @@ export const Step = React.forwardRef(
( > {RenderIcon} - ( {...{ isCurrentStep }} />
- {(isCurrentStep || isCompletedStep) && children} - +
) } ) -Step.displayName = "Step" +StepperItem.displayName = "StepperItem" -/********** StepLabel **********/ +/********** StepperItemLabel **********/ -interface StepLabelProps { +interface StepperItemLabelProps { label: string | React.ReactNode description?: string | React.ReactNode optional?: boolean @@ -354,7 +354,7 @@ interface StepLabelProps { descriptionClassName?: string } -const StepLabel = ({ +const StepperItemLabel = ({ isCurrentStep, label, description, @@ -362,7 +362,7 @@ const StepLabel = ({ optionalLabel, labelClassName, descriptionClassName, -}: StepLabelProps & { +}: StepperItemLabelProps & { isCurrentStep?: boolean }) => { const { isLabelVertical } = useStepperContext() @@ -390,25 +390,30 @@ const StepLabel = ({

)} {!!description && ( -

{description}

+

+ {description} +

)}
) : null } -StepLabel.displayName = "StepLabel" +StepperItemLabel.displayName = "StepperItemLabel" -/********** Connector **********/ +/********** StepperItemConnector **********/ -interface ConnectorProps extends React.HTMLAttributes { +interface StepperItemConnectorProps + extends React.HTMLAttributes { isCompletedStep: boolean isLastStep?: boolean | null hasLabel?: boolean index: number } -const Connector = React.memo( - ({ isCompletedStep, children, isLastStep }: ConnectorProps) => { +const StepperItemConnector = React.memo( + ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => { const { isVertical } = useStepperContext() if (isVertical) { @@ -442,4 +447,4 @@ const Connector = React.memo( } ) -Connector.displayName = "Connector" +StepperItemConnector.displayName = "StepperItemConnector" diff --git a/apps/www/registry/default/ui/use-stepper.ts b/apps/www/registry/default/ui/use-stepper.ts index 01d2163bcad..56f6cc81b23 100644 --- a/apps/www/registry/default/ui/use-stepper.ts +++ b/apps/www/registry/default/ui/use-stepper.ts @@ -1,11 +1,11 @@ import * as React from "react" -import { StepProps } from "./stepper" +import { StepperItemProps } from "./stepper" type useStepper = { initialStep: number steps: Pick< - StepProps, + StepperItemProps, "label" | "description" | "optional" | "optionalLabel" | "icon" >[] } diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx index fca74542c3a..a796595c0b8 100644 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperClickableSteps() { const { @@ -25,15 +29,15 @@ export default function StepperClickableSteps() { return (
- setStep(step)} activeStep={activeStep}> + setStep(step)} activeStep={activeStep}> {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx index d2bb7dc82cb..debb4713a79 100644 --- a/apps/www/registry/new-york/example/stepper-custom-icons.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-icons.tsx @@ -1,14 +1,18 @@ import { Calendar, Twitter, User } from "lucide-react" import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1", icon: }, { label: "Step 2", icon: }, { label: "Step 3", icon: }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperCustomIcons() { const { @@ -26,15 +30,15 @@ export default function StepperCustomIcons() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx b/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx index 1d08a950aeb..7069bdef706 100644 --- a/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx @@ -1,14 +1,18 @@ import { CheckCircle2, XCircle } from "lucide-react" import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperCustomSuccessErrorIcon() { const { @@ -26,19 +30,19 @@ export default function StepperCustomSuccessErrorIcon() { return (
- } errorIcon={} > {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index 45d87fd65fb..45141343110 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperDemo() { const { @@ -24,15 +28,15 @@ export default function StepperDemo() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-description.tsx b/apps/www/registry/new-york/example/stepper-description.tsx index 1012723467f..f6020fbd823 100644 --- a/apps/www/registry/new-york/example/stepper-description.tsx +++ b/apps/www/registry/new-york/example/stepper-description.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1", description: "Frist description" }, { label: "Step 2", description: "Second description" }, { label: "Step 3", description: "Third description" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperDescriptions() { const { @@ -24,15 +28,15 @@ export default function StepperDescriptions() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-label-orientation.tsx b/apps/www/registry/new-york/example/stepper-label-orientation.tsx index dce707f12a9..2b7bd5fff27 100644 --- a/apps/www/registry/new-york/example/stepper-label-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-label-orientation.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1", description: "Frist description" }, { label: "Step 2", description: "Second description" }, { label: "Step 3", description: "Third description" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperLabelOrientation() { const { @@ -24,15 +28,15 @@ export default function StepperLabelOrientation() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx index 6ae93006ee1..0249cd14bc2 100644 --- a/apps/www/registry/new-york/example/stepper-optional-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-optional-steps.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2", optional: true, optionalLabel: "Optional" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperOptionalSteps() { const { @@ -24,15 +28,15 @@ export default function StepperOptionalSteps() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index 1f936f17c34..1339310c480 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -1,12 +1,16 @@ import { Button } from "@/registry/new-york/ui/button" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperOrientation() { const { @@ -24,15 +28,15 @@ export default function StepperOrientation() { return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/example/stepper-states.tsx b/apps/www/registry/new-york/example/stepper-states.tsx index 8a06ad69f23..b5413faf6d8 100644 --- a/apps/www/registry/new-york/example/stepper-states.tsx +++ b/apps/www/registry/new-york/example/stepper-states.tsx @@ -3,14 +3,18 @@ import { useState } from "react" import { Button } from "@/registry/new-york/ui/button" import { Label } from "@/registry/new-york/ui/label" import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" -import { Step, StepConfig, Steps } from "@/registry/new-york/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, +} from "@/registry/new-york/ui/stepper" import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export default function StepperStates() { const { @@ -45,15 +49,15 @@ export default function StepperStates() {
- + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 1d7c44fa3f8..abcc7902980 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -8,9 +8,9 @@ import { Button } from "./button" import { Separator } from "./separator" import { useMediaQuery } from "./use-stepper" -/********** Context **********/ +/********** StepperContext **********/ -interface StepsContextValue extends StepsProps { +interface StepperContextValue extends StepperProps { isClickable?: boolean isError?: boolean isLoading?: boolean @@ -19,14 +19,14 @@ interface StepsContextValue extends StepsProps { stepCount?: number } -const StepsContext = React.createContext({ +const StepperContext = React.createContext({ activeStep: 0, }) -export const useStepperContext = () => React.useContext(StepsContext) +export const useStepperContext = () => React.useContext(StepperContext) -export const StepsProvider: React.FC<{ - value: StepsContextValue +export const StepperProvider: React.FC<{ + value: StepperContextValue children: React.ReactNode }> = ({ value, children }) => { const isError = value.state === "error" @@ -37,7 +37,7 @@ export const StepsProvider: React.FC<{ value.orientation !== "vertical" && value.labelOrientation === "vertical" return ( - {children} - + ) } -/********** Steps **********/ +/********** Stepper **********/ -export interface StepsProps extends React.HTMLAttributes { +export interface StepperProps extends React.HTMLAttributes { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -66,7 +66,7 @@ export interface StepsProps extends React.HTMLAttributes { variant?: "default" | "ghost" | "outline" | "secondary" } -export const Steps = React.forwardRef( +export const Stepper = React.forwardRef( ( { activeStep = 0, @@ -108,7 +108,7 @@ export const Steps = React.forwardRef( const orientation = isMobile && responsive ? "vertical" : orientationProp return ( - ( })}
{orientation === "horizontal" && renderHorizontalContent()} - + ) } ) -Steps.displayName = "Steps" +Stepper.displayName = "Stepper" -/********** Step **********/ +/********** StepperItem **********/ -const stepVariants = cva("relative flex flex-row gap-2", { +const stepperItemVariants = cva("relative flex flex-row gap-2", { variants: { isLastStep: { true: "flex-[0_0_auto] justify-end", @@ -186,24 +186,24 @@ const stepVariants = cva("relative flex flex-row gap-2", { ], }) -export interface StepConfig extends StepLabelProps { +export interface StepperConfig extends StepperItemLabelProps { icon?: React.ReactElement } -export interface StepProps +interface StepProps extends React.HTMLAttributes, - VariantProps, - StepConfig { + VariantProps, + StepperConfig { isCompletedStep?: boolean } -interface StepStatus { +interface StepperItemStatus { index: number isCompletedStep?: boolean isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus { +export interface StepperItemProps extends StepProps, StepperItemStatus { additionalClassName?: { button?: string label?: string @@ -211,7 +211,7 @@ interface StepAndStatusProps extends StepProps, StepStatus { } } -export const Step = React.forwardRef( +export const StepperItem = React.forwardRef( (props, ref) => { const { children, @@ -284,7 +284,7 @@ export const Step = React.forwardRef(
( > {RenderIcon} - ( {...{ isCurrentStep }} />
- {(isCurrentStep || isCompletedStep) && children} - +
) } ) -Step.displayName = "Step" +StepperItem.displayName = "StepperItem" -/********** StepLabel **********/ +/********** StepperItemLabel **********/ -interface StepLabelProps { +interface StepperItemLabelProps { label: string | React.ReactNode description?: string | React.ReactNode optional?: boolean @@ -353,8 +353,7 @@ interface StepLabelProps { labelClassName?: string descriptionClassName?: string } - -const StepLabel = ({ +const StepperItemLabel = ({ isCurrentStep, label, description, @@ -362,7 +361,7 @@ const StepLabel = ({ optionalLabel, labelClassName, descriptionClassName, -}: StepLabelProps & { +}: StepperItemLabelProps & { isCurrentStep?: boolean }) => { const { isLabelVertical } = useStepperContext() @@ -390,25 +389,30 @@ const StepLabel = ({

)} {!!description && ( -

{description}

+

+ {description} +

)}
) : null } -StepLabel.displayName = "StepLabel" +StepperItemLabel.displayName = "StepperItemLabel" -/********** Connector **********/ +/********** StepperItemConnector **********/ -interface ConnectorProps extends React.HTMLAttributes { +interface StepperItemConnectorProps + extends React.HTMLAttributes { isCompletedStep: boolean isLastStep?: boolean | null hasLabel?: boolean index: number } -const Connector = React.memo( - ({ isCompletedStep, children, isLastStep }: ConnectorProps) => { +const StepperItemConnector = React.memo( + ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => { const { isVertical } = useStepperContext() if (isVertical) { @@ -442,4 +446,4 @@ const Connector = React.memo( } ) -Connector.displayName = "Connector" +StepperItemConnector.displayName = "StepperItemConnector" diff --git a/apps/www/registry/new-york/ui/use-stepper.ts b/apps/www/registry/new-york/ui/use-stepper.ts index 01d2163bcad..56f6cc81b23 100644 --- a/apps/www/registry/new-york/ui/use-stepper.ts +++ b/apps/www/registry/new-york/ui/use-stepper.ts @@ -1,11 +1,11 @@ import * as React from "react" -import { StepProps } from "./stepper" +import { StepperItemProps } from "./stepper" type useStepper = { initialStep: number steps: Pick< - StepProps, + StepperItemProps, "label" | "description" | "optional" | "optionalLabel" | "icon" >[] } From bc0050ef4c4153464d59f5a78f98a78da15ef7cf Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 18 Dec 2023 00:48:18 -0300 Subject: [PATCH 08/62] chore: add use stepper to manual docs and update components --- apps/www/content/docs/components/stepper.mdx | 45 ++++++++++++-------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 052e0dc6c48..a28f564424b 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -49,7 +49,13 @@ export default function Page() { Copy and paste the following code into your project. - +`stepper.tsx` + + + +`use-stepper.tsx` + + Update the import paths to match your project setup. @@ -77,7 +83,12 @@ export default function Page() { ## Usage ```tsx -import { Step, Steps, useStepper } from "@/components/ui/stepper" +import { + Stepper, + StepperConfig, + StepperItem, + useStepper, +} from "@/components/ui/stepper" ``` ```tsx {1-5} @@ -85,7 +96,7 @@ const steps = [ { label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }, -] satisfies StepConfig[] +] satisfies StepperConfig[] export const StepperDemo = () => { const { @@ -104,15 +115,15 @@ export const StepperDemo = () => { return ( <> - + {steps.map((step, index) => ( - +

Step {index + 1} content

-
+ ))} -
+
{activeStep === steps.length ? ( <> @@ -137,10 +148,10 @@ export const StepperDemo = () => { ## API -### `` +### `` ```tsx -interface StepsProps extends React.HTMLAttributes { +interface StepperProps extends React.HTMLAttributes { activeStep: number orientation?: "vertical" | "horizontal" state?: "loading" | "error" @@ -154,34 +165,34 @@ interface StepsProps extends React.HTMLAttributes { } ``` -### `` +### `` ```tsx -interface StepLabelProps { +interface StepperItemLabelProps { label: string | React.ReactNode description?: string | React.ReactNode optional?: boolean optionalLabel?: string | React.ReactNode } -interface StepConfig extends StepLabelProps { +interface StepperConfig extends StepperItemLabelProps { icon?: React.ReactElement } -interface StepProps +interface StepperItemProps extends React.HTMLAttributes, - VariantProps, - StepConfig { + VariantProps, + StepperConfig { isCompletedStep?: boolean } -interface StepStatus { +interface StepperItemStatus { index: number isCompletedStep?: boolean isCurrentStep?: boolean } -interface StepAndStatusProps extends StepProps, StepStatus { +interface StepperItemProps extends StepperItemProps, StepperItemStatus { additionalClassName?: { button?: string label?: string From 2ab9354f344c243e49515ad325d1b4026ca83703 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 24 Dec 2023 01:59:49 -0300 Subject: [PATCH 09/62] refactor: wip --- apps/www/__registry__/index.tsx | 1489 ++++++----------- apps/www/content/docs/components/stepper.mdx | 54 - apps/www/public/registry/index.json | 349 +++- .../registry/styles/default/stepper.json | 4 +- .../registry/styles/new-york/stepper.json | 4 +- .../new-york/example/stepper-demo.tsx | 29 +- apps/www/registry/new-york/ui/stepper.tsx | 540 +++--- apps/www/registry/new-york/ui/use-stepper.ts | 124 -- apps/www/registry/new-york/ui/use-stepper.tsx | 59 + 9 files changed, 1075 insertions(+), 1577 deletions(-) delete mode 100644 apps/www/registry/new-york/ui/use-stepper.ts create mode 100644 apps/www/registry/new-york/ui/use-stepper.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 209a67238db..e59281f419f 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -4,15 +4,15 @@ import * as React from "react" export const Index: Record = { - default: { - accordion: { + "default": { + "accordion": { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/accordion")), files: ["registry/default/ui/accordion.tsx"], }, - alert: { + "alert": { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -33,63 +33,63 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/aspect-ratio")), files: ["registry/default/ui/aspect-ratio.tsx"], }, - avatar: { + "avatar": { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/avatar")), files: ["registry/default/ui/avatar.tsx"], }, - badge: { + "badge": { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/badge")), files: ["registry/default/ui/badge.tsx"], }, - button: { + "button": { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/button")), files: ["registry/default/ui/button.tsx"], }, - calendar: { + "calendar": { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/calendar")), files: ["registry/default/ui/calendar.tsx"], }, - card: { + "card": { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/card")), files: ["registry/default/ui/card.tsx"], }, - carousel: { + "carousel": { name: "carousel", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/carousel")), files: ["registry/default/ui/carousel.tsx"], }, - checkbox: { + "checkbox": { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/checkbox")), files: ["registry/default/ui/checkbox.tsx"], }, - collapsible: { + "collapsible": { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/collapsible")), files: ["registry/default/ui/collapsible.tsx"], }, - command: { + "command": { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -103,14 +103,14 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/context-menu")), files: ["registry/default/ui/context-menu.tsx"], }, - dialog: { + "dialog": { name: "dialog", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/dialog")), files: ["registry/default/ui/dialog.tsx"], }, - drawer: { + "drawer": { name: "drawer", type: "components:ui", registryDependencies: undefined, @@ -121,15 +121,13 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/ui/dropdown-menu") - ), + component: React.lazy(() => import("@/registry/default/ui/dropdown-menu")), files: ["registry/default/ui/dropdown-menu.tsx"], }, - form: { + "form": { name: "form", type: "components:ui", - registryDependencies: ["button", "label"], + registryDependencies: ["button","label"], component: React.lazy(() => import("@/registry/default/ui/form")), files: ["registry/default/ui/form.tsx"], }, @@ -140,21 +138,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/hover-card")), files: ["registry/default/ui/hover-card.tsx"], }, - input: { + "input": { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/input")), files: ["registry/default/ui/input.tsx"], }, - label: { + "label": { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/label")), files: ["registry/default/ui/label.tsx"], }, - menubar: { + "menubar": { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -165,26 +163,24 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/ui/navigation-menu") - ), + component: React.lazy(() => import("@/registry/default/ui/navigation-menu")), files: ["registry/default/ui/navigation-menu.tsx"], }, - pagination: { + "pagination": { name: "pagination", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/pagination")), files: ["registry/default/ui/pagination.tsx"], }, - popover: { + "popover": { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/popover")), files: ["registry/default/ui/popover.tsx"], }, - progress: { + "progress": { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -198,7 +194,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/radio-group")), files: ["registry/default/ui/radio-group.tsx"], }, - resizable: { + "resizable": { name: "resizable", type: "components:ui", registryDependencies: undefined, @@ -212,88 +208,91 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/scroll-area")), files: ["registry/default/ui/scroll-area.tsx"], }, - select: { + "select": { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/select")), files: ["registry/default/ui/select.tsx"], }, - separator: { + "separator": { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/separator")), files: ["registry/default/ui/separator.tsx"], }, - sheet: { + "sheet": { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sheet")), files: ["registry/default/ui/sheet.tsx"], }, - skeleton: { + "skeleton": { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/skeleton")), files: ["registry/default/ui/skeleton.tsx"], }, - slider: { + "slider": { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/slider")), files: ["registry/default/ui/slider.tsx"], }, - sonner: { + "sonner": { name: "sonner", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sonner")), files: ["registry/default/ui/sonner.tsx"], }, - switch: { + "stepper": { + name: "stepper", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/ui/stepper")), + files: ["registry/default/ui/stepper.tsx","registry/default/ui/use-stepper.ts"], + }, + "switch": { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/switch")), files: ["registry/default/ui/switch.tsx"], }, - table: { + "table": { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/table")), files: ["registry/default/ui/table.tsx"], }, - tabs: { + "tabs": { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/tabs")), files: ["registry/default/ui/tabs.tsx"], }, - textarea: { + "textarea": { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/textarea")), files: ["registry/default/ui/textarea.tsx"], }, - toast: { + "toast": { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/toast")), - files: [ - "registry/default/ui/toast.tsx", - "registry/default/ui/use-toast.ts", - "registry/default/ui/toaster.tsx", - ], + files: ["registry/default/ui/toast.tsx","registry/default/ui/use-toast.ts","registry/default/ui/toaster.tsx"], }, - toggle: { + "toggle": { name: "toggle", type: "components:ui", registryDependencies: undefined, @@ -307,7 +306,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/toggle-group")), files: ["registry/default/ui/toggle-group.tsx"], }, - tooltip: { + "tooltip": { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -318,1189 +317,991 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy( - () => import("@/registry/default/example/accordion-demo") - ), + component: React.lazy(() => import("@/registry/default/example/accordion-demo")), files: ["registry/default/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/default/example/alert-demo") - ), + component: React.lazy(() => import("@/registry/default/example/alert-demo")), files: ["registry/default/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/default/example/alert-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/alert-destructive")), files: ["registry/default/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog", "button"], - component: React.lazy( - () => import("@/registry/default/example/alert-dialog-demo") - ), + registryDependencies: ["alert-dialog","button"], + component: React.lazy(() => import("@/registry/default/example/alert-dialog-demo")), files: ["registry/default/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy( - () => import("@/registry/default/example/aspect-ratio-demo") - ), + component: React.lazy(() => import("@/registry/default/example/aspect-ratio-demo")), files: ["registry/default/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy( - () => import("@/registry/default/example/avatar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/avatar-demo")), files: ["registry/default/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-demo") - ), + component: React.lazy(() => import("@/registry/default/example/badge-demo")), files: ["registry/default/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/badge-destructive")), files: ["registry/default/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-outline") - ), + component: React.lazy(() => import("@/registry/default/example/badge-outline")), files: ["registry/default/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-secondary") - ), + component: React.lazy(() => import("@/registry/default/example/badge-secondary")), files: ["registry/default/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-demo") - ), + component: React.lazy(() => import("@/registry/default/example/button-demo")), files: ["registry/default/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-secondary") - ), + component: React.lazy(() => import("@/registry/default/example/button-secondary")), files: ["registry/default/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/button-destructive")), files: ["registry/default/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-outline") - ), + component: React.lazy(() => import("@/registry/default/example/button-outline")), files: ["registry/default/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-ghost") - ), + component: React.lazy(() => import("@/registry/default/example/button-ghost")), files: ["registry/default/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-link") - ), + component: React.lazy(() => import("@/registry/default/example/button-link")), files: ["registry/default/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-with-icon") - ), + component: React.lazy(() => import("@/registry/default/example/button-with-icon")), files: ["registry/default/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-loading") - ), + component: React.lazy(() => import("@/registry/default/example/button-loading")), files: ["registry/default/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-icon") - ), + component: React.lazy(() => import("@/registry/default/example/button-icon")), files: ["registry/default/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-as-child") - ), + component: React.lazy(() => import("@/registry/default/example/button-as-child")), files: ["registry/default/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy( - () => import("@/registry/default/example/calendar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/calendar-demo")), files: ["registry/default/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/default/example/calendar-form") - ), + registryDependencies: ["calendar","form","popover"], + component: React.lazy(() => import("@/registry/default/example/calendar-form")), files: ["registry/default/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card", "button", "switch"], - component: React.lazy( - () => import("@/registry/default/example/card-demo") - ), + registryDependencies: ["card","button","switch"], + component: React.lazy(() => import("@/registry/default/example/card-demo")), files: ["registry/default/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button", "card", "input", "label", "select"], - component: React.lazy( - () => import("@/registry/default/example/card-with-form") - ), + registryDependencies: ["button","card","input","label","select"], + component: React.lazy(() => import("@/registry/default/example/card-with-form")), files: ["registry/default/example/card-with-form.tsx"], }, "carousel-demo": { name: "carousel-demo", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-demo") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-demo")), files: ["registry/default/example/carousel-demo.tsx"], }, "carousel-size": { name: "carousel-size", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-size") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-size")), files: ["registry/default/example/carousel-size.tsx"], }, "carousel-spacing": { name: "carousel-spacing", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-spacing") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-spacing")), files: ["registry/default/example/carousel-spacing.tsx"], }, "carousel-orientation": { name: "carousel-orientation", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-orientation") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-orientation")), files: ["registry/default/example/carousel-orientation.tsx"], }, "carousel-api": { name: "carousel-api", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-api") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-api")), files: ["registry/default/example/carousel-api.tsx"], }, "carousel-plugin": { name: "carousel-plugin", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-plugin") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-plugin")), files: ["registry/default/example/carousel-plugin.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-demo") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-demo")), files: ["registry/default/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-disabled")), files: ["registry/default/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-form-multiple") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/default/example/checkbox-form-multiple")), files: ["registry/default/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-form-single") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/default/example/checkbox-form-single")), files: ["registry/default/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-with-text") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-with-text")), files: ["registry/default/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy( - () => import("@/registry/default/example/collapsible-demo") - ), + component: React.lazy(() => import("@/registry/default/example/collapsible-demo")), files: ["registry/default/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/default/example/combobox-demo") - ), + component: React.lazy(() => import("@/registry/default/example/combobox-demo")), files: ["registry/default/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command", "dropdown-menu", "button"], - component: React.lazy( - () => import("@/registry/default/example/combobox-dropdown-menu") - ), + registryDependencies: ["command","dropdown-menu","button"], + component: React.lazy(() => import("@/registry/default/example/combobox-dropdown-menu")), files: ["registry/default/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command", "form"], - component: React.lazy( - () => import("@/registry/default/example/combobox-form") - ), + registryDependencies: ["command","form"], + component: React.lazy(() => import("@/registry/default/example/combobox-form")), files: ["registry/default/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox", "popover"], - component: React.lazy( - () => import("@/registry/default/example/combobox-popover") - ), + registryDependencies: ["combobox","popover"], + component: React.lazy(() => import("@/registry/default/example/combobox-popover")), files: ["registry/default/example/combobox-popover.tsx"], }, "combobox-responsive": { name: "combobox-responsive", type: "components:example", - registryDependencies: ["combobox", "popover", "drawer"], - component: React.lazy( - () => import("@/registry/default/example/combobox-responsive") - ), + registryDependencies: ["combobox","popover","drawer"], + component: React.lazy(() => import("@/registry/default/example/combobox-responsive")), files: ["registry/default/example/combobox-responsive.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/default/example/command-demo") - ), + component: React.lazy(() => import("@/registry/default/example/command-demo")), files: ["registry/default/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command", "dialog"], - component: React.lazy( - () => import("@/registry/default/example/command-dialog") - ), + registryDependencies: ["command","dialog"], + component: React.lazy(() => import("@/registry/default/example/command-dialog")), files: ["registry/default/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy( - () => import("@/registry/default/example/context-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/context-menu-demo")), files: ["registry/default/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy( - () => import("@/registry/default/example/data-table-demo") - ), + component: React.lazy(() => import("@/registry/default/example/data-table-demo")), files: ["registry/default/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-demo") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-demo")), files: ["registry/default/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button", "calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-form") - ), + registryDependencies: ["button","calendar","form","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-form")), files: ["registry/default/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button", "calendar", "popover", "select"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-with-presets") - ), + registryDependencies: ["button","calendar","popover","select"], + component: React.lazy(() => import("@/registry/default/example/date-picker-with-presets")), files: ["registry/default/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-with-range") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-with-range")), files: ["registry/default/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy( - () => import("@/registry/default/example/dialog-demo") - ), + component: React.lazy(() => import("@/registry/default/example/dialog-demo")), files: ["registry/default/example/dialog-demo.tsx"], }, "dialog-close-button": { name: "dialog-close-button", type: "components:example", - registryDependencies: ["dialog", "button"], - component: React.lazy( - () => import("@/registry/default/example/dialog-close-button") - ), + registryDependencies: ["dialog","button"], + component: React.lazy(() => import("@/registry/default/example/dialog-close-button")), files: ["registry/default/example/dialog-close-button.tsx"], }, "drawer-demo": { name: "drawer-demo", type: "components:example", registryDependencies: ["drawer"], - component: React.lazy( - () => import("@/registry/default/example/drawer-demo") - ), + component: React.lazy(() => import("@/registry/default/example/drawer-demo")), files: ["registry/default/example/drawer-demo.tsx"], }, "drawer-dialog": { name: "drawer-dialog", type: "components:example", - registryDependencies: ["drawer", "dialog"], - component: React.lazy( - () => import("@/registry/default/example/drawer-dialog") - ), + registryDependencies: ["drawer","dialog"], + component: React.lazy(() => import("@/registry/default/example/drawer-dialog")), files: ["registry/default/example/drawer-dialog.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-demo")), files: ["registry/default/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu", "checkbox"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-checkboxes") - ), + registryDependencies: ["dropdown-menu","checkbox"], + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-checkboxes")), files: ["registry/default/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu", "radio-group"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-radio-group") - ), + registryDependencies: ["dropdown-menu","radio-group"], + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-radio-group")), files: ["registry/default/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy( - () => import("@/registry/default/example/hover-card-demo") - ), + component: React.lazy(() => import("@/registry/default/example/hover-card-demo")), files: ["registry/default/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-demo") - ), + component: React.lazy(() => import("@/registry/default/example/input-demo")), files: ["registry/default/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/input-disabled")), files: ["registry/default/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-file") - ), + component: React.lazy(() => import("@/registry/default/example/input-file")), files: ["registry/default/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input", "button", "form"], - component: React.lazy( - () => import("@/registry/default/example/input-form") - ), + registryDependencies: ["input","button","form"], + component: React.lazy(() => import("@/registry/default/example/input-form")), files: ["registry/default/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input", "button"], - component: React.lazy( - () => import("@/registry/default/example/input-with-button") - ), + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/default/example/input-with-button")), files: ["registry/default/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/default/example/input-with-label") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/default/example/input-with-label")), files: ["registry/default/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/default/example/input-with-text") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/default/example/input-with-text")), files: ["registry/default/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy( - () => import("@/registry/default/example/label-demo") - ), + component: React.lazy(() => import("@/registry/default/example/label-demo")), files: ["registry/default/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy( - () => import("@/registry/default/example/menubar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/menubar-demo")), files: ["registry/default/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy( - () => import("@/registry/default/example/navigation-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/navigation-menu-demo")), files: ["registry/default/example/navigation-menu-demo.tsx"], }, "pagination-demo": { name: "pagination-demo", type: "components:example", registryDependencies: ["pagination"], - component: React.lazy( - () => import("@/registry/default/example/pagination-demo") - ), + component: React.lazy(() => import("@/registry/default/example/pagination-demo")), files: ["registry/default/example/pagination-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy( - () => import("@/registry/default/example/popover-demo") - ), + component: React.lazy(() => import("@/registry/default/example/popover-demo")), files: ["registry/default/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy( - () => import("@/registry/default/example/progress-demo") - ), + component: React.lazy(() => import("@/registry/default/example/progress-demo")), files: ["registry/default/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy( - () => import("@/registry/default/example/radio-group-demo") - ), + component: React.lazy(() => import("@/registry/default/example/radio-group-demo")), files: ["registry/default/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group", "form"], - component: React.lazy( - () => import("@/registry/default/example/radio-group-form") - ), + registryDependencies: ["radio-group","form"], + component: React.lazy(() => import("@/registry/default/example/radio-group-form")), files: ["registry/default/example/radio-group-form.tsx"], }, "resizable-demo": { name: "resizable-demo", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-demo") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-demo")), files: ["registry/default/example/resizable-demo.tsx"], }, "resizable-demo-with-handle": { name: "resizable-demo-with-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-demo-with-handle") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-demo-with-handle")), files: ["registry/default/example/resizable-demo-with-handle.tsx"], }, "resizable-vertical": { name: "resizable-vertical", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-vertical") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-vertical")), files: ["registry/default/example/resizable-vertical.tsx"], }, "resizable-handle": { name: "resizable-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-handle") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-handle")), files: ["registry/default/example/resizable-handle.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/default/example/scroll-area-demo") - ), + component: React.lazy(() => import("@/registry/default/example/scroll-area-demo")), files: ["registry/default/example/scroll-area-demo.tsx"], }, "scroll-area-horizontal-demo": { name: "scroll-area-horizontal-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/default/example/scroll-area-horizontal-demo") - ), + component: React.lazy(() => import("@/registry/default/example/scroll-area-horizontal-demo")), files: ["registry/default/example/scroll-area-horizontal-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-demo") - ), + component: React.lazy(() => import("@/registry/default/example/select-demo")), files: ["registry/default/example/select-demo.tsx"], }, "select-scrollable": { name: "select-scrollable", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-scrollable") - ), + component: React.lazy(() => import("@/registry/default/example/select-scrollable")), files: ["registry/default/example/select-scrollable.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-form") - ), + component: React.lazy(() => import("@/registry/default/example/select-form")), files: ["registry/default/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy( - () => import("@/registry/default/example/separator-demo") - ), + component: React.lazy(() => import("@/registry/default/example/separator-demo")), files: ["registry/default/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/default/example/sheet-demo") - ), + component: React.lazy(() => import("@/registry/default/example/sheet-demo")), files: ["registry/default/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/default/example/sheet-side") - ), + component: React.lazy(() => import("@/registry/default/example/sheet-side")), files: ["registry/default/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy( - () => import("@/registry/default/example/skeleton-demo") - ), + component: React.lazy(() => import("@/registry/default/example/skeleton-demo")), files: ["registry/default/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy( - () => import("@/registry/default/example/slider-demo") - ), + component: React.lazy(() => import("@/registry/default/example/slider-demo")), files: ["registry/default/example/slider-demo.tsx"], }, "sonner-demo": { name: "sonner-demo", type: "components:example", registryDependencies: ["sonner"], - component: React.lazy( - () => import("@/registry/default/example/sonner-demo") - ), + component: React.lazy(() => import("@/registry/default/example/sonner-demo")), files: ["registry/default/example/sonner-demo.tsx"], }, + "stepper-demo": { + name: "stepper-demo", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-demo")), + files: ["registry/default/example/stepper-demo.tsx"], + }, + "stepper-orientation": { + name: "stepper-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-orientation")), + files: ["registry/default/example/stepper-orientation.tsx"], + }, + "stepper-description": { + name: "stepper-description", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-description")), + files: ["registry/default/example/stepper-description.tsx"], + }, + "stepper-custom-icons": { + name: "stepper-custom-icons", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-custom-icons")), + files: ["registry/default/example/stepper-custom-icons.tsx"], + }, + "stepper-custom-success-error-icon": { + name: "stepper-custom-success-error-icon", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-custom-success-error-icon")), + files: ["registry/default/example/stepper-custom-success-error-icon.tsx"], + }, + "stepper-label-orientation": { + name: "stepper-label-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-label-orientation")), + files: ["registry/default/example/stepper-label-orientation.tsx"], + }, + "stepper-clickable-steps": { + name: "stepper-clickable-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-clickable-steps")), + files: ["registry/default/example/stepper-clickable-steps.tsx"], + }, + "stepper-optional-steps": { + name: "stepper-optional-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-optional-steps")), + files: ["registry/default/example/stepper-optional-steps.tsx"], + }, + "stepper-states": { + name: "stepper-states", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-states")), + files: ["registry/default/example/stepper-states.tsx"], + }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy( - () => import("@/registry/default/example/switch-demo") - ), + component: React.lazy(() => import("@/registry/default/example/switch-demo")), files: ["registry/default/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch", "form"], - component: React.lazy( - () => import("@/registry/default/example/switch-form") - ), + registryDependencies: ["switch","form"], + component: React.lazy(() => import("@/registry/default/example/switch-form")), files: ["registry/default/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy( - () => import("@/registry/default/example/table-demo") - ), + component: React.lazy(() => import("@/registry/default/example/table-demo")), files: ["registry/default/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy( - () => import("@/registry/default/example/tabs-demo") - ), + component: React.lazy(() => import("@/registry/default/example/tabs-demo")), files: ["registry/default/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/default/example/textarea-demo") - ), + component: React.lazy(() => import("@/registry/default/example/textarea-demo")), files: ["registry/default/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/default/example/textarea-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/textarea-disabled")), files: ["registry/default/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea", "form"], - component: React.lazy( - () => import("@/registry/default/example/textarea-form") - ), + registryDependencies: ["textarea","form"], + component: React.lazy(() => import("@/registry/default/example/textarea-form")), files: ["registry/default/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea", "button"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-button") - ), + registryDependencies: ["textarea","button"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-button")), files: ["registry/default/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-label") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-label")), files: ["registry/default/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-text") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-text")), files: ["registry/default/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toast-demo")), files: ["registry/default/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/toast-destructive")), files: ["registry/default/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-simple") - ), + component: React.lazy(() => import("@/registry/default/example/toast-simple")), files: ["registry/default/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-with-action") - ), + component: React.lazy(() => import("@/registry/default/example/toast-with-action")), files: ["registry/default/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-with-title") - ), + component: React.lazy(() => import("@/registry/default/example/toast-with-title")), files: ["registry/default/example/toast-with-title.tsx"], }, "toggle-group-demo": { name: "toggle-group-demo", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-demo")), files: ["registry/default/example/toggle-group-demo.tsx"], }, "toggle-group-disabled": { name: "toggle-group-disabled", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-disabled")), files: ["registry/default/example/toggle-group-disabled.tsx"], }, "toggle-group-lg": { name: "toggle-group-lg", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-lg") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-lg")), files: ["registry/default/example/toggle-group-lg.tsx"], }, "toggle-group-outline": { name: "toggle-group-outline", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-outline") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-outline")), files: ["registry/default/example/toggle-group-outline.tsx"], }, "toggle-group-sm": { name: "toggle-group-sm", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-sm") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-sm")), files: ["registry/default/example/toggle-group-sm.tsx"], }, "toggle-group-single": { name: "toggle-group-single", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-single") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-single")), files: ["registry/default/example/toggle-group-single.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-demo")), files: ["registry/default/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-disabled")), files: ["registry/default/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-lg") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-lg")), files: ["registry/default/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-outline") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-outline")), files: ["registry/default/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-sm") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-sm")), files: ["registry/default/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-with-text") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-with-text")), files: ["registry/default/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy( - () => import("@/registry/default/example/tooltip-demo") - ), + component: React.lazy(() => import("@/registry/default/example/tooltip-demo")), files: ["registry/default/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-blockquote") - ), + component: React.lazy(() => import("@/registry/default/example/typography-blockquote")), files: ["registry/default/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-demo") - ), + component: React.lazy(() => import("@/registry/default/example/typography-demo")), files: ["registry/default/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h1") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h1")), files: ["registry/default/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h2") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h2")), files: ["registry/default/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h3") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h3")), files: ["registry/default/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h4") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h4")), files: ["registry/default/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-inline-code") - ), + component: React.lazy(() => import("@/registry/default/example/typography-inline-code")), files: ["registry/default/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-large") - ), + component: React.lazy(() => import("@/registry/default/example/typography-large")), files: ["registry/default/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-lead") - ), + component: React.lazy(() => import("@/registry/default/example/typography-lead")), files: ["registry/default/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-list") - ), + component: React.lazy(() => import("@/registry/default/example/typography-list")), files: ["registry/default/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-muted") - ), + component: React.lazy(() => import("@/registry/default/example/typography-muted")), files: ["registry/default/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-p") - ), + component: React.lazy(() => import("@/registry/default/example/typography-p")), files: ["registry/default/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-small") - ), + component: React.lazy(() => import("@/registry/default/example/typography-small")), files: ["registry/default/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-table") - ), + component: React.lazy(() => import("@/registry/default/example/typography-table")), files: ["registry/default/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/mode-toggle") - ), + component: React.lazy(() => import("@/registry/default/example/mode-toggle")), files: ["registry/default/example/mode-toggle.tsx"], }, - cards: { + "cards": { name: "cards", type: "components:example", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/example/cards")), files: ["registry/default/example/cards/cards.tsx"], }, - }, - "new-york": { - accordion: { + }, "new-york": { + "accordion": { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/accordion")), files: ["registry/new-york/ui/accordion.tsx"], }, - alert: { + "alert": { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -1511,77 +1312,73 @@ export const Index: Record = { name: "alert-dialog", type: "components:ui", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/ui/alert-dialog") - ), + component: React.lazy(() => import("@/registry/new-york/ui/alert-dialog")), files: ["registry/new-york/ui/alert-dialog.tsx"], }, "aspect-ratio": { name: "aspect-ratio", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/aspect-ratio") - ), + component: React.lazy(() => import("@/registry/new-york/ui/aspect-ratio")), files: ["registry/new-york/ui/aspect-ratio.tsx"], }, - avatar: { + "avatar": { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/avatar")), files: ["registry/new-york/ui/avatar.tsx"], }, - badge: { + "badge": { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/badge")), files: ["registry/new-york/ui/badge.tsx"], }, - button: { + "button": { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/button")), files: ["registry/new-york/ui/button.tsx"], }, - calendar: { + "calendar": { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/calendar")), files: ["registry/new-york/ui/calendar.tsx"], }, - card: { + "card": { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/card")), files: ["registry/new-york/ui/card.tsx"], }, - carousel: { + "carousel": { name: "carousel", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/carousel")), files: ["registry/new-york/ui/carousel.tsx"], }, - checkbox: { + "checkbox": { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/checkbox")), files: ["registry/new-york/ui/checkbox.tsx"], }, - collapsible: { + "collapsible": { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/collapsible")), files: ["registry/new-york/ui/collapsible.tsx"], }, - command: { + "command": { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -1592,19 +1389,17 @@ export const Index: Record = { name: "context-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/context-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/context-menu")), files: ["registry/new-york/ui/context-menu.tsx"], }, - dialog: { + "dialog": { name: "dialog", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/dialog")), files: ["registry/new-york/ui/dialog.tsx"], }, - drawer: { + "drawer": { name: "drawer", type: "components:ui", registryDependencies: undefined, @@ -1615,15 +1410,13 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/dropdown-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/dropdown-menu")), files: ["registry/new-york/ui/dropdown-menu.tsx"], }, - form: { + "form": { name: "form", type: "components:ui", - registryDependencies: ["button", "label"], + registryDependencies: ["button","label"], component: React.lazy(() => import("@/registry/new-york/ui/form")), files: ["registry/new-york/ui/form.tsx"], }, @@ -1634,21 +1427,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/hover-card")), files: ["registry/new-york/ui/hover-card.tsx"], }, - input: { + "input": { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/input")), files: ["registry/new-york/ui/input.tsx"], }, - label: { + "label": { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/label")), files: ["registry/new-york/ui/label.tsx"], }, - menubar: { + "menubar": { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -1659,26 +1452,24 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/navigation-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/navigation-menu")), files: ["registry/new-york/ui/navigation-menu.tsx"], }, - pagination: { + "pagination": { name: "pagination", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/pagination")), files: ["registry/new-york/ui/pagination.tsx"], }, - popover: { + "popover": { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/popover")), files: ["registry/new-york/ui/popover.tsx"], }, - progress: { + "progress": { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -1692,7 +1483,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/radio-group")), files: ["registry/new-york/ui/radio-group.tsx"], }, - resizable: { + "resizable": { name: "resizable", type: "components:ui", registryDependencies: undefined, @@ -1706,88 +1497,91 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/scroll-area")), files: ["registry/new-york/ui/scroll-area.tsx"], }, - select: { + "select": { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/select")), files: ["registry/new-york/ui/select.tsx"], }, - separator: { + "separator": { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/separator")), files: ["registry/new-york/ui/separator.tsx"], }, - sheet: { + "sheet": { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sheet")), files: ["registry/new-york/ui/sheet.tsx"], }, - skeleton: { + "skeleton": { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/skeleton")), files: ["registry/new-york/ui/skeleton.tsx"], }, - slider: { + "slider": { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/slider")), files: ["registry/new-york/ui/slider.tsx"], }, - sonner: { + "sonner": { name: "sonner", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sonner")), files: ["registry/new-york/ui/sonner.tsx"], }, - switch: { + "stepper": { + name: "stepper", + type: "components:ui", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/new-york/ui/stepper")), + files: ["registry/new-york/ui/stepper.tsx","registry/new-york/ui/use-stepper.ts"], + }, + "switch": { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/switch")), files: ["registry/new-york/ui/switch.tsx"], }, - table: { + "table": { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/table")), files: ["registry/new-york/ui/table.tsx"], }, - tabs: { + "tabs": { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/tabs")), files: ["registry/new-york/ui/tabs.tsx"], }, - textarea: { + "textarea": { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/textarea")), files: ["registry/new-york/ui/textarea.tsx"], }, - toast: { + "toast": { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/toast")), - files: [ - "registry/new-york/ui/toast.tsx", - "registry/new-york/ui/use-toast.ts", - "registry/new-york/ui/toaster.tsx", - ], + files: ["registry/new-york/ui/toast.tsx","registry/new-york/ui/use-toast.ts","registry/new-york/ui/toaster.tsx"], }, - toggle: { + "toggle": { name: "toggle", type: "components:ui", registryDependencies: undefined, @@ -1798,12 +1592,10 @@ export const Index: Record = { name: "toggle-group", type: "components:ui", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/ui/toggle-group") - ), + component: React.lazy(() => import("@/registry/new-york/ui/toggle-group")), files: ["registry/new-york/ui/toggle-group.tsx"], }, - tooltip: { + "tooltip": { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -1814,1173 +1606,976 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy( - () => import("@/registry/new-york/example/accordion-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/accordion-demo")), files: ["registry/new-york/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/alert-demo")), files: ["registry/new-york/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/alert-destructive")), files: ["registry/new-york/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-dialog-demo") - ), + registryDependencies: ["alert-dialog","button"], + component: React.lazy(() => import("@/registry/new-york/example/alert-dialog-demo")), files: ["registry/new-york/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy( - () => import("@/registry/new-york/example/aspect-ratio-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/aspect-ratio-demo")), files: ["registry/new-york/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy( - () => import("@/registry/new-york/example/avatar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/avatar-demo")), files: ["registry/new-york/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-demo")), files: ["registry/new-york/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-destructive")), files: ["registry/new-york/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-outline")), files: ["registry/new-york/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-secondary") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-secondary")), files: ["registry/new-york/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-demo")), files: ["registry/new-york/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-secondary") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-secondary")), files: ["registry/new-york/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-destructive")), files: ["registry/new-york/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-outline")), files: ["registry/new-york/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-ghost") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-ghost")), files: ["registry/new-york/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-link") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-link")), files: ["registry/new-york/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-with-icon") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-with-icon")), files: ["registry/new-york/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-loading") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-loading")), files: ["registry/new-york/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-icon") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-icon")), files: ["registry/new-york/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-as-child") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-as-child")), files: ["registry/new-york/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy( - () => import("@/registry/new-york/example/calendar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/calendar-demo")), files: ["registry/new-york/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/calendar-form") - ), + registryDependencies: ["calendar","form","popover"], + component: React.lazy(() => import("@/registry/new-york/example/calendar-form")), files: ["registry/new-york/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card", "button", "switch"], - component: React.lazy( - () => import("@/registry/new-york/example/card-demo") - ), + registryDependencies: ["card","button","switch"], + component: React.lazy(() => import("@/registry/new-york/example/card-demo")), files: ["registry/new-york/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button", "card", "input", "label", "select"], - component: React.lazy( - () => import("@/registry/new-york/example/card-with-form") - ), + registryDependencies: ["button","card","input","label","select"], + component: React.lazy(() => import("@/registry/new-york/example/card-with-form")), files: ["registry/new-york/example/card-with-form.tsx"], }, "carousel-demo": { name: "carousel-demo", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-demo")), files: ["registry/new-york/example/carousel-demo.tsx"], }, "carousel-size": { name: "carousel-size", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-size") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-size")), files: ["registry/new-york/example/carousel-size.tsx"], }, "carousel-spacing": { name: "carousel-spacing", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-spacing") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-spacing")), files: ["registry/new-york/example/carousel-spacing.tsx"], }, "carousel-orientation": { name: "carousel-orientation", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-orientation") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-orientation")), files: ["registry/new-york/example/carousel-orientation.tsx"], }, "carousel-api": { name: "carousel-api", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-api") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-api")), files: ["registry/new-york/example/carousel-api.tsx"], }, "carousel-plugin": { name: "carousel-plugin", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-plugin") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-plugin")), files: ["registry/new-york/example/carousel-plugin.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-demo")), files: ["registry/new-york/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-disabled")), files: ["registry/new-york/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-form-multiple") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-multiple")), files: ["registry/new-york/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-form-single") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-single")), files: ["registry/new-york/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-with-text") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-with-text")), files: ["registry/new-york/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy( - () => import("@/registry/new-york/example/collapsible-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/collapsible-demo")), files: ["registry/new-york/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/combobox-demo")), files: ["registry/new-york/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command", "dropdown-menu", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-dropdown-menu") - ), + registryDependencies: ["command","dropdown-menu","button"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-dropdown-menu")), files: ["registry/new-york/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-form") - ), + registryDependencies: ["command","form"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-form")), files: ["registry/new-york/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-popover") - ), + registryDependencies: ["combobox","popover"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-popover")), files: ["registry/new-york/example/combobox-popover.tsx"], }, "combobox-responsive": { name: "combobox-responsive", type: "components:example", - registryDependencies: ["combobox", "popover", "drawer"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-responsive") - ), + registryDependencies: ["combobox","popover","drawer"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-responsive")), files: ["registry/new-york/example/combobox-responsive.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/new-york/example/command-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/command-demo")), files: ["registry/new-york/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command", "dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/command-dialog") - ), + registryDependencies: ["command","dialog"], + component: React.lazy(() => import("@/registry/new-york/example/command-dialog")), files: ["registry/new-york/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/context-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/context-menu-demo")), files: ["registry/new-york/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy( - () => import("@/registry/new-york/example/data-table-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/data-table-demo")), files: ["registry/new-york/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-demo") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-demo")), files: ["registry/new-york/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button", "calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-form") - ), + registryDependencies: ["button","calendar","form","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-form")), files: ["registry/new-york/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button", "calendar", "popover", "select"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-with-presets") - ), + registryDependencies: ["button","calendar","popover","select"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-presets")), files: ["registry/new-york/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-with-range") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-range")), files: ["registry/new-york/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/dialog-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/dialog-demo")), files: ["registry/new-york/example/dialog-demo.tsx"], }, "dialog-close-button": { name: "dialog-close-button", type: "components:example", - registryDependencies: ["dialog", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/dialog-close-button") - ), + registryDependencies: ["dialog","button"], + component: React.lazy(() => import("@/registry/new-york/example/dialog-close-button")), files: ["registry/new-york/example/dialog-close-button.tsx"], }, "drawer-demo": { name: "drawer-demo", type: "components:example", registryDependencies: ["drawer"], - component: React.lazy( - () => import("@/registry/new-york/example/drawer-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/drawer-demo")), files: ["registry/new-york/example/drawer-demo.tsx"], }, "drawer-dialog": { name: "drawer-dialog", type: "components:example", - registryDependencies: ["drawer", "dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/drawer-dialog") - ), + registryDependencies: ["drawer","dialog"], + component: React.lazy(() => import("@/registry/new-york/example/drawer-dialog")), files: ["registry/new-york/example/drawer-dialog.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-demo")), files: ["registry/new-york/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu", "checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-checkboxes") - ), + registryDependencies: ["dropdown-menu","checkbox"], + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-checkboxes")), files: ["registry/new-york/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu", "radio-group"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-radio-group") - ), + registryDependencies: ["dropdown-menu","radio-group"], + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-radio-group")), files: ["registry/new-york/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy( - () => import("@/registry/new-york/example/hover-card-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/hover-card-demo")), files: ["registry/new-york/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-demo")), files: ["registry/new-york/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-disabled")), files: ["registry/new-york/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-file") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-file")), files: ["registry/new-york/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input", "button", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/input-form") - ), + registryDependencies: ["input","button","form"], + component: React.lazy(() => import("@/registry/new-york/example/input-form")), files: ["registry/new-york/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-button") - ), + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-button")), files: ["registry/new-york/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-label") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-label")), files: ["registry/new-york/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-text") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-text")), files: ["registry/new-york/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy( - () => import("@/registry/new-york/example/label-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/label-demo")), files: ["registry/new-york/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy( - () => import("@/registry/new-york/example/menubar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/menubar-demo")), files: ["registry/new-york/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/navigation-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/navigation-menu-demo")), files: ["registry/new-york/example/navigation-menu-demo.tsx"], }, "pagination-demo": { name: "pagination-demo", type: "components:example", registryDependencies: ["pagination"], - component: React.lazy( - () => import("@/registry/new-york/example/pagination-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/pagination-demo")), files: ["registry/new-york/example/pagination-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy( - () => import("@/registry/new-york/example/popover-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/popover-demo")), files: ["registry/new-york/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy( - () => import("@/registry/new-york/example/progress-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/progress-demo")), files: ["registry/new-york/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy( - () => import("@/registry/new-york/example/radio-group-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/radio-group-demo")), files: ["registry/new-york/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/radio-group-form") - ), + registryDependencies: ["radio-group","form"], + component: React.lazy(() => import("@/registry/new-york/example/radio-group-form")), files: ["registry/new-york/example/radio-group-form.tsx"], }, "resizable-demo": { name: "resizable-demo", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-demo")), files: ["registry/new-york/example/resizable-demo.tsx"], }, "resizable-demo-with-handle": { name: "resizable-demo-with-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-demo-with-handle") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-demo-with-handle")), files: ["registry/new-york/example/resizable-demo-with-handle.tsx"], }, "resizable-vertical": { name: "resizable-vertical", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-vertical") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-vertical")), files: ["registry/new-york/example/resizable-vertical.tsx"], }, "resizable-handle": { name: "resizable-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-handle") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-handle")), files: ["registry/new-york/example/resizable-handle.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/new-york/example/scroll-area-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/scroll-area-demo")), files: ["registry/new-york/example/scroll-area-demo.tsx"], }, "scroll-area-horizontal-demo": { name: "scroll-area-horizontal-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/new-york/example/scroll-area-horizontal-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/scroll-area-horizontal-demo")), files: ["registry/new-york/example/scroll-area-horizontal-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-demo")), files: ["registry/new-york/example/select-demo.tsx"], }, "select-scrollable": { name: "select-scrollable", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-scrollable") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-scrollable")), files: ["registry/new-york/example/select-scrollable.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-form") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-form")), files: ["registry/new-york/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy( - () => import("@/registry/new-york/example/separator-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/separator-demo")), files: ["registry/new-york/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/new-york/example/sheet-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/sheet-demo")), files: ["registry/new-york/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/new-york/example/sheet-side") - ), + component: React.lazy(() => import("@/registry/new-york/example/sheet-side")), files: ["registry/new-york/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy( - () => import("@/registry/new-york/example/skeleton-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/skeleton-demo")), files: ["registry/new-york/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy( - () => import("@/registry/new-york/example/slider-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/slider-demo")), files: ["registry/new-york/example/slider-demo.tsx"], }, "sonner-demo": { name: "sonner-demo", type: "components:example", registryDependencies: ["sonner"], - component: React.lazy( - () => import("@/registry/new-york/example/sonner-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/sonner-demo")), files: ["registry/new-york/example/sonner-demo.tsx"], }, + "stepper-demo": { + name: "stepper-demo", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-demo")), + files: ["registry/new-york/example/stepper-demo.tsx"], + }, + "stepper-orientation": { + name: "stepper-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-orientation")), + files: ["registry/new-york/example/stepper-orientation.tsx"], + }, + "stepper-description": { + name: "stepper-description", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-description")), + files: ["registry/new-york/example/stepper-description.tsx"], + }, + "stepper-custom-icons": { + name: "stepper-custom-icons", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-icons")), + files: ["registry/new-york/example/stepper-custom-icons.tsx"], + }, + "stepper-custom-success-error-icon": { + name: "stepper-custom-success-error-icon", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-success-error-icon")), + files: ["registry/new-york/example/stepper-custom-success-error-icon.tsx"], + }, + "stepper-label-orientation": { + name: "stepper-label-orientation", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-label-orientation")), + files: ["registry/new-york/example/stepper-label-orientation.tsx"], + }, + "stepper-clickable-steps": { + name: "stepper-clickable-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-clickable-steps")), + files: ["registry/new-york/example/stepper-clickable-steps.tsx"], + }, + "stepper-optional-steps": { + name: "stepper-optional-steps", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-optional-steps")), + files: ["registry/new-york/example/stepper-optional-steps.tsx"], + }, + "stepper-states": { + name: "stepper-states", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-states")), + files: ["registry/new-york/example/stepper-states.tsx"], + }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy( - () => import("@/registry/new-york/example/switch-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/switch-demo")), files: ["registry/new-york/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/switch-form") - ), + registryDependencies: ["switch","form"], + component: React.lazy(() => import("@/registry/new-york/example/switch-form")), files: ["registry/new-york/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy( - () => import("@/registry/new-york/example/table-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/table-demo")), files: ["registry/new-york/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy( - () => import("@/registry/new-york/example/tabs-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/tabs-demo")), files: ["registry/new-york/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/textarea-demo")), files: ["registry/new-york/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/textarea-disabled")), files: ["registry/new-york/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-form") - ), + registryDependencies: ["textarea","form"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-form")), files: ["registry/new-york/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-button") - ), + registryDependencies: ["textarea","button"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-button")), files: ["registry/new-york/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-label") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-label")), files: ["registry/new-york/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-text") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-text")), files: ["registry/new-york/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-demo")), files: ["registry/new-york/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-destructive")), files: ["registry/new-york/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-simple") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-simple")), files: ["registry/new-york/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-with-action") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-with-action")), files: ["registry/new-york/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-with-title") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-with-title")), files: ["registry/new-york/example/toast-with-title.tsx"], }, "toggle-group-demo": { name: "toggle-group-demo", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-demo")), files: ["registry/new-york/example/toggle-group-demo.tsx"], }, "toggle-group-disabled": { name: "toggle-group-disabled", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-disabled")), files: ["registry/new-york/example/toggle-group-disabled.tsx"], }, "toggle-group-lg": { name: "toggle-group-lg", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-lg") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-lg")), files: ["registry/new-york/example/toggle-group-lg.tsx"], }, "toggle-group-outline": { name: "toggle-group-outline", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-outline")), files: ["registry/new-york/example/toggle-group-outline.tsx"], }, "toggle-group-sm": { name: "toggle-group-sm", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-sm") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-sm")), files: ["registry/new-york/example/toggle-group-sm.tsx"], }, "toggle-group-single": { name: "toggle-group-single", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-single") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-single")), files: ["registry/new-york/example/toggle-group-single.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-demo")), files: ["registry/new-york/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-disabled")), files: ["registry/new-york/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-lg") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-lg")), files: ["registry/new-york/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-outline")), files: ["registry/new-york/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-sm") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-sm")), files: ["registry/new-york/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-with-text") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-with-text")), files: ["registry/new-york/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy( - () => import("@/registry/new-york/example/tooltip-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/tooltip-demo")), files: ["registry/new-york/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-blockquote") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-blockquote")), files: ["registry/new-york/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-demo")), files: ["registry/new-york/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h1") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h1")), files: ["registry/new-york/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h2") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h2")), files: ["registry/new-york/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h3") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h3")), files: ["registry/new-york/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h4") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h4")), files: ["registry/new-york/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-inline-code") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-inline-code")), files: ["registry/new-york/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-large") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-large")), files: ["registry/new-york/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-lead") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-lead")), files: ["registry/new-york/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-list") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-list")), files: ["registry/new-york/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-muted") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-muted")), files: ["registry/new-york/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-p") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-p")), files: ["registry/new-york/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-small") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-small")), files: ["registry/new-york/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-table") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-table")), files: ["registry/new-york/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/mode-toggle") - ), + component: React.lazy(() => import("@/registry/new-york/example/mode-toggle")), files: ["registry/new-york/example/mode-toggle.tsx"], }, - cards: { + "cards": { name: "cards", type: "components:example", registryDependencies: undefined, diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index a28f564424b..8a84ea90c7a 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -232,57 +232,3 @@ function useStepper( ### Default - ---- - -### Orientation - -We can pass the `orientation` prop to the Steps component to set the orientation as "vertical" or "horizontal". - - - -### Descriptions - -The Step component also accepts a `description` prop which can be used to provide some extra information about the step. - - - -### Custom Icons - -If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component. - - - -### Custom Success/Error icon - -If you want to show a custom success/error icon instead of the default, you can do so by using the `successIcon` and `errorIcon` prop on the Steps component. - - - -### Label orientation - -If you would like the labels to be positioned below the step icons you can do so using the `labelOrientation` prop on the Steps component. - - - -### States - -Sometimes it is useful to show visual feedback to the user depending on some asynchronous logic. In this case we can use the `state` prop. - - - -### Clickable Steps - -By providing the onClickStep prop the steps will become clickable. - - - -### Optional Steps - -If you want to make a step optional, you can use the value of optional and optionalLabel in the array of steps. - - - -### Responsive - -By default, the stepper behaves vertically on small screens. If you need to remove that functionality, you can change the `responsive` prop to false in the Steps component. diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index 77ba2b20477..9c1e0c777b1 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -1,105 +1,177 @@ [ { "name": "accordion", - "dependencies": ["@radix-ui/react-accordion"], - "files": ["ui/accordion.tsx"], + "dependencies": [ + "@radix-ui/react-accordion" + ], + "files": [ + "ui/accordion.tsx" + ], "type": "components:ui" }, { "name": "alert", - "files": ["ui/alert.tsx"], + "files": [ + "ui/alert.tsx" + ], "type": "components:ui" }, { "name": "alert-dialog", - "dependencies": ["@radix-ui/react-alert-dialog"], - "registryDependencies": ["button"], - "files": ["ui/alert-dialog.tsx"], + "dependencies": [ + "@radix-ui/react-alert-dialog" + ], + "registryDependencies": [ + "button" + ], + "files": [ + "ui/alert-dialog.tsx" + ], "type": "components:ui" }, { "name": "aspect-ratio", - "dependencies": ["@radix-ui/react-aspect-ratio"], - "files": ["ui/aspect-ratio.tsx"], + "dependencies": [ + "@radix-ui/react-aspect-ratio" + ], + "files": [ + "ui/aspect-ratio.tsx" + ], "type": "components:ui" }, { "name": "avatar", - "dependencies": ["@radix-ui/react-avatar"], - "files": ["ui/avatar.tsx"], + "dependencies": [ + "@radix-ui/react-avatar" + ], + "files": [ + "ui/avatar.tsx" + ], "type": "components:ui" }, { "name": "badge", - "files": ["ui/badge.tsx"], + "files": [ + "ui/badge.tsx" + ], "type": "components:ui" }, { "name": "button", - "dependencies": ["@radix-ui/react-slot"], - "files": ["ui/button.tsx"], + "dependencies": [ + "@radix-ui/react-slot" + ], + "files": [ + "ui/button.tsx" + ], "type": "components:ui" }, { "name": "calendar", - "dependencies": ["react-day-picker", "date-fns"], - "registryDependencies": ["button"], - "files": ["ui/calendar.tsx"], + "dependencies": [ + "react-day-picker", + "date-fns" + ], + "registryDependencies": [ + "button" + ], + "files": [ + "ui/calendar.tsx" + ], "type": "components:ui" }, { "name": "card", - "files": ["ui/card.tsx"], + "files": [ + "ui/card.tsx" + ], "type": "components:ui" }, { "name": "carousel", - "dependencies": ["embla-carousel-react"], - "registryDependencies": ["button"], - "files": ["ui/carousel.tsx"], + "dependencies": [ + "embla-carousel-react" + ], + "registryDependencies": [ + "button" + ], + "files": [ + "ui/carousel.tsx" + ], "type": "components:ui" }, { "name": "checkbox", - "dependencies": ["@radix-ui/react-checkbox"], - "files": ["ui/checkbox.tsx"], + "dependencies": [ + "@radix-ui/react-checkbox" + ], + "files": [ + "ui/checkbox.tsx" + ], "type": "components:ui" }, { "name": "collapsible", - "dependencies": ["@radix-ui/react-collapsible"], - "files": ["ui/collapsible.tsx"], + "dependencies": [ + "@radix-ui/react-collapsible" + ], + "files": [ + "ui/collapsible.tsx" + ], "type": "components:ui" }, { "name": "command", - "dependencies": ["cmdk"], - "registryDependencies": ["dialog"], - "files": ["ui/command.tsx"], + "dependencies": [ + "cmdk" + ], + "registryDependencies": [ + "dialog" + ], + "files": [ + "ui/command.tsx" + ], "type": "components:ui" }, { "name": "context-menu", - "dependencies": ["@radix-ui/react-context-menu"], - "files": ["ui/context-menu.tsx"], + "dependencies": [ + "@radix-ui/react-context-menu" + ], + "files": [ + "ui/context-menu.tsx" + ], "type": "components:ui" }, { "name": "dialog", - "dependencies": ["@radix-ui/react-dialog"], - "files": ["ui/dialog.tsx"], + "dependencies": [ + "@radix-ui/react-dialog" + ], + "files": [ + "ui/dialog.tsx" + ], "type": "components:ui" }, { "name": "drawer", - "dependencies": ["vaul", "@radix-ui/react-dialog"], - "files": ["ui/drawer.tsx"], + "dependencies": [ + "vaul", + "@radix-ui/react-dialog" + ], + "files": [ + "ui/drawer.tsx" + ], "type": "components:ui" }, { "name": "dropdown-menu", - "dependencies": ["@radix-ui/react-dropdown-menu"], - "files": ["ui/dropdown-menu.tsx"], + "dependencies": [ + "@radix-ui/react-dropdown-menu" + ], + "files": [ + "ui/dropdown-menu.tsx" + ], "type": "components:ui" }, { @@ -111,160 +183,265 @@ "zod", "react-hook-form" ], - "registryDependencies": ["button", "label"], - "files": ["ui/form.tsx"], + "registryDependencies": [ + "button", + "label" + ], + "files": [ + "ui/form.tsx" + ], "type": "components:ui" }, { "name": "hover-card", - "dependencies": ["@radix-ui/react-hover-card"], - "files": ["ui/hover-card.tsx"], + "dependencies": [ + "@radix-ui/react-hover-card" + ], + "files": [ + "ui/hover-card.tsx" + ], "type": "components:ui" }, { "name": "input", - "files": ["ui/input.tsx"], + "files": [ + "ui/input.tsx" + ], "type": "components:ui" }, { "name": "label", - "dependencies": ["@radix-ui/react-label"], - "files": ["ui/label.tsx"], + "dependencies": [ + "@radix-ui/react-label" + ], + "files": [ + "ui/label.tsx" + ], "type": "components:ui" }, { "name": "menubar", - "dependencies": ["@radix-ui/react-menubar"], - "files": ["ui/menubar.tsx"], + "dependencies": [ + "@radix-ui/react-menubar" + ], + "files": [ + "ui/menubar.tsx" + ], "type": "components:ui" }, { "name": "navigation-menu", - "dependencies": ["@radix-ui/react-navigation-menu"], - "files": ["ui/navigation-menu.tsx"], + "dependencies": [ + "@radix-ui/react-navigation-menu" + ], + "files": [ + "ui/navigation-menu.tsx" + ], "type": "components:ui" }, { "name": "pagination", - "registryDependencies": ["button"], - "files": ["ui/pagination.tsx"], + "registryDependencies": [ + "button" + ], + "files": [ + "ui/pagination.tsx" + ], "type": "components:ui" }, { "name": "popover", - "dependencies": ["@radix-ui/react-popover"], - "files": ["ui/popover.tsx"], + "dependencies": [ + "@radix-ui/react-popover" + ], + "files": [ + "ui/popover.tsx" + ], "type": "components:ui" }, { "name": "progress", - "dependencies": ["@radix-ui/react-progress"], - "files": ["ui/progress.tsx"], + "dependencies": [ + "@radix-ui/react-progress" + ], + "files": [ + "ui/progress.tsx" + ], "type": "components:ui" }, { "name": "radio-group", - "dependencies": ["@radix-ui/react-radio-group"], - "files": ["ui/radio-group.tsx"], + "dependencies": [ + "@radix-ui/react-radio-group" + ], + "files": [ + "ui/radio-group.tsx" + ], "type": "components:ui" }, { "name": "resizable", - "dependencies": ["react-resizable-panels"], - "files": ["ui/resizable.tsx"], + "dependencies": [ + "react-resizable-panels" + ], + "files": [ + "ui/resizable.tsx" + ], "type": "components:ui" }, { "name": "scroll-area", - "dependencies": ["@radix-ui/react-scroll-area"], - "files": ["ui/scroll-area.tsx"], + "dependencies": [ + "@radix-ui/react-scroll-area" + ], + "files": [ + "ui/scroll-area.tsx" + ], "type": "components:ui" }, { "name": "select", - "dependencies": ["@radix-ui/react-select"], - "files": ["ui/select.tsx"], + "dependencies": [ + "@radix-ui/react-select" + ], + "files": [ + "ui/select.tsx" + ], "type": "components:ui" }, { "name": "separator", - "dependencies": ["@radix-ui/react-separator"], - "files": ["ui/separator.tsx"], + "dependencies": [ + "@radix-ui/react-separator" + ], + "files": [ + "ui/separator.tsx" + ], "type": "components:ui" }, { "name": "sheet", - "dependencies": ["@radix-ui/react-dialog"], - "files": ["ui/sheet.tsx"], + "dependencies": [ + "@radix-ui/react-dialog" + ], + "files": [ + "ui/sheet.tsx" + ], "type": "components:ui" }, { "name": "skeleton", - "files": ["ui/skeleton.tsx"], + "files": [ + "ui/skeleton.tsx" + ], "type": "components:ui" }, { "name": "slider", - "dependencies": ["@radix-ui/react-slider"], - "files": ["ui/slider.tsx"], + "dependencies": [ + "@radix-ui/react-slider" + ], + "files": [ + "ui/slider.tsx" + ], "type": "components:ui" }, { "name": "sonner", - "dependencies": ["sonner", "next-themes"], - "files": ["ui/sonner.tsx"], + "dependencies": [ + "sonner", + "next-themes" + ], + "files": [ + "ui/sonner.tsx" + ], "type": "components:ui" }, { "name": "stepper", - "files": ["ui/stepper.tsx", "ui/use-stepper.ts"], + "files": [ + "ui/stepper.tsx", + "ui/use-stepper.ts" + ], "type": "components:ui" }, { "name": "switch", - "dependencies": ["@radix-ui/react-switch"], - "files": ["ui/switch.tsx"], + "dependencies": [ + "@radix-ui/react-switch" + ], + "files": [ + "ui/switch.tsx" + ], "type": "components:ui" }, { "name": "table", - "files": ["ui/table.tsx"], + "files": [ + "ui/table.tsx" + ], "type": "components:ui" }, { "name": "tabs", - "dependencies": ["@radix-ui/react-tabs"], - "files": ["ui/tabs.tsx"], + "dependencies": [ + "@radix-ui/react-tabs" + ], + "files": [ + "ui/tabs.tsx" + ], "type": "components:ui" }, { "name": "textarea", - "files": ["ui/textarea.tsx"], + "files": [ + "ui/textarea.tsx" + ], "type": "components:ui" }, { "name": "toast", - "dependencies": ["@radix-ui/react-toast"], - "files": ["ui/toast.tsx", "ui/use-toast.ts", "ui/toaster.tsx"], + "dependencies": [ + "@radix-ui/react-toast" + ], + "files": [ + "ui/toast.tsx", + "ui/use-toast.ts", + "ui/toaster.tsx" + ], "type": "components:ui" }, { "name": "toggle", - "dependencies": ["@radix-ui/react-toggle"], - "files": ["ui/toggle.tsx"], + "dependencies": [ + "@radix-ui/react-toggle" + ], + "files": [ + "ui/toggle.tsx" + ], "type": "components:ui" }, { "name": "toggle-group", - "dependencies": ["@radix-ui/react-toggle-group"], - "registryDependencies": ["toggle"], - "files": ["ui/toggle-group.tsx"], + "dependencies": [ + "@radix-ui/react-toggle-group" + ], + "registryDependencies": [ + "toggle" + ], + "files": [ + "ui/toggle-group.tsx" + ], "type": "components:ui" }, { "name": "tooltip", - "dependencies": ["@radix-ui/react-tooltip"], - "files": ["ui/tooltip.tsx"], + "dependencies": [ + "@radix-ui/react-tooltip" + ], + "files": [ + "ui/tooltip.tsx" + ], "type": "components:ui" } -] +] \ No newline at end of file diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index 9df984a3f62..344f41cb617 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,11 +3,11 @@ "files": [ { "name": "stepper.tsx", - "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** Context **********/\n\ninterface StepsContextValue extends StepsProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepsContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepsContext)\n\nexport const StepsProvider: React.FC<{\n value: StepsContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Steps **********/\n\nexport interface StepsProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Steps = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nSteps.displayName = \"Steps\"\n\n/********** Step **********/\n\nconst stepVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepConfig extends StepLabelProps {\n icon?: React.ReactElement\n}\n\nexport interface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\ninterface StepAndStatusProps extends StepProps, StepStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const Step = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStep.displayName = \"Step\"\n\n/********** StepLabel **********/\n\ninterface StepLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\n\nconst StepLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n

{description}

\n )}\n
\n ) : null\n}\n\nStepLabel.displayName = \"StepLabel\"\n\n/********** Connector **********/\n\ninterface ConnectorProps extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst Connector = React.memo(\n ({ isCompletedStep, children, isLastStep }: ConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nConnector.displayName = \"Connector\"\n" + "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** StepperContext **********/\n\ninterface StepperContextValue extends StepperProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepperContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepperContext)\n\nexport const StepperProvider: React.FC<{\n value: StepperContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Stepper **********/\n\nexport interface StepperProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Stepper = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nStepper.displayName = \"Stepper\"\n\n/********** StepperItem **********/\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepperConfig extends StepperItemLabelProps {\n icon?: React.ReactElement\n}\n\ninterface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepperConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepperItemStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\nexport interface StepperItemProps extends StepProps, StepperItemStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const StepperItem = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStepperItem.displayName = \"StepperItem\"\n\n/********** StepperItemLabel **********/\n\ninterface StepperItemLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\n\nconst StepperItemLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepperItemLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n \n {description}\n

\n )}\n
\n ) : null\n}\n\nStepperItemLabel.displayName = \"StepperItemLabel\"\n\n/********** StepperItemConnector **********/\n\ninterface StepperItemConnectorProps\n extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst StepperItemConnector = React.memo(\n ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nStepperItemConnector.displayName = \"StepperItemConnector\"\n" }, { "name": "use-stepper.ts", - "content": "import * as React from \"react\"\n\nimport { StepProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" + "content": "import * as React from \"react\"\n\nimport { StepperItemProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepperItemProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index e10aaefb48a..0aa13aad00d 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,11 +3,11 @@ "files": [ { "name": "stepper.tsx", - "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** Context **********/\n\ninterface StepsContextValue extends StepsProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepsContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepsContext)\n\nexport const StepsProvider: React.FC<{\n value: StepsContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Steps **********/\n\nexport interface StepsProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Steps = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nSteps.displayName = \"Steps\"\n\n/********** Step **********/\n\nconst stepVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepConfig extends StepLabelProps {\n icon?: React.ReactElement\n}\n\nexport interface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\ninterface StepAndStatusProps extends StepProps, StepStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const Step = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStep.displayName = \"Step\"\n\n/********** StepLabel **********/\n\ninterface StepLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\n\nconst StepLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n

{description}

\n )}\n
\n ) : null\n}\n\nStepLabel.displayName = \"StepLabel\"\n\n/********** Connector **********/\n\ninterface ConnectorProps extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst Connector = React.memo(\n ({ isCompletedStep, children, isLastStep }: ConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nConnector.displayName = \"Connector\"\n" + "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** StepperContext **********/\n\ninterface StepperContextValue extends StepperProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepperContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepperContext)\n\nexport const StepperProvider: React.FC<{\n value: StepperContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Stepper **********/\n\nexport interface StepperProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Stepper = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nStepper.displayName = \"Stepper\"\n\n/********** StepperItem **********/\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepperConfig extends StepperItemLabelProps {\n icon?: React.ReactElement\n}\n\ninterface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepperConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepperItemStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\nexport interface StepperItemProps extends StepProps, StepperItemStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const StepperItem = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStepperItem.displayName = \"StepperItem\"\n\n/********** StepperItemLabel **********/\n\ninterface StepperItemLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\nconst StepperItemLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepperItemLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n \n {description}\n

\n )}\n
\n ) : null\n}\n\nStepperItemLabel.displayName = \"StepperItemLabel\"\n\n/********** StepperItemConnector **********/\n\ninterface StepperItemConnectorProps\n extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst StepperItemConnector = React.memo(\n ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nStepperItemConnector.displayName = \"StepperItemConnector\"\n" }, { "name": "use-stepper.ts", - "content": "import * as React from \"react\"\n\nimport { StepProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" + "content": "import * as React from \"react\"\n\nimport { StepperItemProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepperItemProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" } ], "type": "components:ui" diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index 45141343110..edfe851b3f8 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -13,31 +13,28 @@ const steps = [ ] satisfies StepperConfig[] export default function StepperDemo() { - const { - nextStep, - prevStep, - resetSteps, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } = useStepper({ - initialStep: 0, - steps, - }) + // const { + // nextStep, + // prevStep, + // resetSteps, + // activeStep, + // isDisabledStep, + // isLastStep, + // isOptionalStep, + // } = useStepper() return (
- + {steps.map((step, index) => ( - +

Step {index + 1} content

))}
-
+ {/*
{activeStep === steps.length ? ( <>

All steps completed!

@@ -53,7 +50,7 @@ export default function StepperDemo() { )} -
+
*/}
) } diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index abcc7902980..5a171cd22bd 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -1,125 +1,147 @@ -import * as React from "react" -import { VariantProps, cva } from "class-variance-authority" -import { Check, Loader2, X } from "lucide-react" +import React from "react" +import { cva } from "class-variance-authority" import { cn } from "@/lib/utils" +import { useMediaQuery } from "@/hooks/use-media-query" import { Button } from "./button" import { Separator } from "./separator" -import { useMediaQuery } from "./use-stepper" -/********** StepperContext **********/ - -interface StepperContextValue extends StepperProps { - isClickable?: boolean - isError?: boolean - isLoading?: boolean - isVertical?: boolean - isLabelVertical?: boolean - stepCount?: number +type UseStepper = { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number + isDisabledStep: boolean + isLastStep: boolean + isOptionalStep: boolean | undefined } -const StepperContext = React.createContext({ - activeStep: 0, -}) +export function useStepper(): UseStepper { + const { steps, initialStep } = useStepperContext() -export const useStepperContext = () => React.useContext(StepperContext) + if (steps.length === 0) { + throw new Error( + "useStepper must be used within a StepperProvider. Wrap a parent component in to fix this error." + ) + } -export const StepperProvider: React.FC<{ - value: StepperContextValue - children: React.ReactNode -}> = ({ value, children }) => { - const isError = value.state === "error" - const isLoading = value.state === "loading" + const [activeStep, setActiveStep] = React.useState(initialStep) - const isVertical = value.orientation === "vertical" - const isLabelVertical = - value.orientation !== "vertical" && value.labelOrientation === "vertical" + const nextStep = () => { + setActiveStep((prev) => prev + 1) + } - return ( - - {children} - - ) -} + const prevStep = () => { + setActiveStep((prev) => prev - 1) + } -/********** Stepper **********/ + const resetSteps = () => { + setActiveStep(initialStep) + } -export interface StepperProps extends React.HTMLAttributes { - activeStep: number + const setStep = (step: number) => { + setActiveStep(step) + } + + const isDisabledStep = activeStep === 0 + + const isLastStep = activeStep === steps.length - 1 + + const isOptionalStep = steps[activeStep]?.optional + + return { + nextStep, + prevStep, + resetSteps, + setStep, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } +} + +interface StepperProps { + initialStep: number + steps: { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + }[] orientation?: "vertical" | "horizontal" - state?: "loading" | "error" - responsive?: boolean - onClickStep?: (step: number) => void - successIcon?: React.ReactElement - errorIcon?: React.ReactElement labelOrientation?: "vertical" | "horizontal" - children?: React.ReactNode + responsive?: boolean variant?: "default" | "ghost" | "outline" | "secondary" + status?: "default" | "success" | "error" | "warning" | "loading" + clickable?: boolean +} + +const StepperContext = React.createContext({ + initialStep: 0, + steps: [], +}) + +export const useStepperContext = () => React.useContext(StepperContext) + +const StepperProvider = ({ + value, + children, +}: { + value: StepperProps + children: React.ReactNode +}) => { + return ( + {children} + ) } -export const Stepper = React.forwardRef( +export const Stepper = React.forwardRef< + HTMLDivElement, + StepperProps & React.HTMLAttributes +>( ( { - activeStep = 0, - state, - responsive = true, + initialStep, + steps, + status = "default", orientation: orientationProp = "horizontal", - onClickStep, + responsive = true, labelOrientation = "horizontal", children, - errorIcon, - successIcon, variant = "default", + clickable = true, className, ...props }, ref ) => { - const childArr = React.Children.toArray(children) - - const stepCount = childArr.length - - const renderHorizontalContent = () => { - if (activeStep <= childArr.length) { - return React.Children.map(childArr[activeStep], (node) => { - if (!React.isValidElement(node)) return - return React.Children.map( - node.props.children, - (childNode) => childNode - ) - }) - } - return null - } - - const isClickable = !!onClickStep - const isMobile = useMediaQuery("(max-width: 43em)") - const orientation = isMobile && responsive ? "vertical" : orientationProp + const childrens = React.Children.toArray(children).map((child, index) => { + if (!React.isValidElement(child)) { + return null + } + if (child.type !== StepperItem) { + return child + } + const stepperItemProps = { + ...child.props, + step: index, + } + return React.cloneElement(child, stepperItemProps) + }) + return ( @@ -132,37 +154,13 @@ export const Stepper = React.forwardRef( className )} > - {React.Children.map(children, (child, i) => { - const isCompletedStep = - (React.isValidElement(child) && child.props.isCompletedStep) ?? - i < activeStep - const isLastStep = i === stepCount - 1 - const isCurrentStep = i === activeStep - - const stepProps = { - index: i, - isCompletedStep, - isCurrentStep, - isLastStep, - } - - if (React.isValidElement(child)) { - return React.cloneElement(child, stepProps) - } - - return null - })} + {childrens}
- {orientation === "horizontal" && renderHorizontalContent()} ) } ) -Stepper.displayName = "Stepper" - -/********** StepperItem **********/ - const stepperItemVariants = cva("relative flex flex-row gap-2", { variants: { isLastStep: { @@ -186,264 +184,114 @@ const stepperItemVariants = cva("relative flex flex-row gap-2", { ], }) -export interface StepperConfig extends StepperItemLabelProps { - icon?: React.ReactElement -} - -interface StepProps - extends React.HTMLAttributes, - VariantProps, - StepperConfig { - isCompletedStep?: boolean -} - -interface StepperItemStatus { - index: number - isCompletedStep?: boolean - isCurrentStep?: boolean -} - -export interface StepperItemProps extends StepProps, StepperItemStatus { - additionalClassName?: { - button?: string - label?: string - description?: string +export const StepperItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & { + icon?: React.ReactNode } -} - -export const StepperItem = React.forwardRef( - (props, ref) => { - const { - children, - description, - icon: CustomIcon, - index, - isCompletedStep, - isCurrentStep, - isLastStep, - label, - optional, - optionalLabel, - className, - additionalClassName, - ...rest - } = props - - const { - isVertical, - isError, - isLoading, - successIcon: CustomSuccessIcon, - errorIcon: CustomErrorIcon, - isLabelVertical, - onClickStep, - isClickable, - variant, - } = useStepperContext() + // @ts-ignore +>(({ step, children, className, onClick, ...props }, ref) => { + const { steps, orientation, labelOrientation, variant, status, clickable } = + useStepperContext() - const hasVisited = isCurrentStep || isCompletedStep + const { activeStep, setStep } = useStepper() - const handleClick = (index: number) => { - if (isClickable && onClickStep) { - onClickStep(index) - } - } + const isLastStep = steps.length - 1 === step - const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon]) + const isActive = step === activeStep + const isCompleted = step < activeStep + const isDisabled = step > activeStep && !clickable + const isVisited = isActive || isCompleted - const Success = React.useMemo( - () => CustomSuccessIcon ?? , - [CustomSuccessIcon] - ) + const isVertical = orientation === "vertical" + const isVerticalLabel = labelOrientation === "vertical" - const Error = React.useMemo( - () => CustomErrorIcon ?? , - [CustomErrorIcon] - ) + const isError = isActive && status === "error" - const RenderIcon = React.useMemo(() => { - if (isCompletedStep) return Success - if (isCurrentStep) { - if (isError) return Error - if (isLoading) return - } - if (Icon) return Icon - return (index || 0) + 1 - }, [ - isCompletedStep, - Success, - isCurrentStep, - Icon, - index, - isError, - Error, - isLoading, - ]) + const onClickItem = (e: React.MouseEvent) => { + if (isDisabled) { + return + } + setStep(step) + onClick?.(e) + } - return ( + return ( +
handleClick(index)} - aria-disabled={!hasVisited} > -
- - -
- - {(isCurrentStep || isCompletedStep) && children} - -
- ) - } -) - -StepperItem.displayName = "StepperItem" - -/********** StepperItemLabel **********/ - -interface StepperItemLabelProps { - label: string | React.ReactNode - description?: string | React.ReactNode - optional?: boolean - optionalLabel?: string | React.ReactNode - labelClassName?: string - descriptionClassName?: string -} -const StepperItemLabel = ({ - isCurrentStep, - label, - description, - optional, - optionalLabel, - labelClassName, - descriptionClassName, -}: StepperItemLabelProps & { - isCurrentStep?: boolean -}) => { - const { isLabelVertical } = useStepperContext() - - const shouldRender = !!label || !!description - - const renderOptionalLabel = !!optional && !!optionalLabel - - return shouldRender ? ( -
- {!!label && ( -

- {label} - {renderOptionalLabel && ( - - ({optionalLabel}) - + {isCompleted ? ( + + + + ) : ( + step + 1 )} -

- )} - {!!description && ( -

- {description} -

- )} -
- ) : null -} - -StepperItemLabel.displayName = "StepperItemLabel" - -/********** StepperItemConnector **********/ - -interface StepperItemConnectorProps - extends React.HTMLAttributes { - isCompletedStep: boolean - isLastStep?: boolean | null - hasLabel?: boolean - index: number -} - -const StepperItemConnector = React.memo( - ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => { - const { isVertical } = useStepperContext() - - if (isVertical) { - return ( +
- {!isCompletedStep && ( -
{children}
+

+ {steps[step].label} +

+ {steps[step].description && ( +

+ {steps[step].description} +

)}
- ) - } - - if (isLastStep) { - return null - } - - return ( - - ) - } -) +
+ {!isLastStep && ( + + )} +
+ ) +}) -StepperItemConnector.displayName = "StepperItemConnector" +StepperItem.displayName = "StepperItem" diff --git a/apps/www/registry/new-york/ui/use-stepper.ts b/apps/www/registry/new-york/ui/use-stepper.ts deleted file mode 100644 index 56f6cc81b23..00000000000 --- a/apps/www/registry/new-york/ui/use-stepper.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as React from "react" - -import { StepperItemProps } from "./stepper" - -type useStepper = { - initialStep: number - steps: Pick< - StepperItemProps, - "label" | "description" | "optional" | "optionalLabel" | "icon" - >[] -} - -type useStepperReturn = { - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void - activeStep: number - isDisabledStep: boolean - isLastStep: boolean - isOptionalStep: boolean | undefined -} - -export function useStepper({ - initialStep, - steps, -}: useStepper): useStepperReturn { - const [activeStep, setActiveStep] = React.useState(initialStep) - - const nextStep = () => { - setActiveStep((prev) => prev + 1) - } - - const prevStep = () => { - setActiveStep((prev) => prev - 1) - } - - const resetSteps = () => { - setActiveStep(initialStep) - } - - const setStep = (step: number) => { - setActiveStep(step) - } - - const isDisabledStep = activeStep === 0 - - const isLastStep = activeStep === steps.length - 1 - - const isOptionalStep = steps[activeStep]?.optional - - return { - nextStep, - prevStep, - resetSteps, - setStep, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } -} - -interface UseMediaQueryOptions { - getInitialValueInEffect: boolean -} - -type MediaQueryCallback = (event: { matches: boolean; media: string }) => void - -/** - * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia - * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent - * */ -function attachMediaListener( - query: MediaQueryList, - callback: MediaQueryCallback -) { - try { - query.addEventListener("change", callback) - return () => query.removeEventListener("change", callback) - } catch (e) { - query.addListener(callback) - return () => query.removeListener(callback) - } -} - -function getInitialValue(query: string, initialValue?: boolean) { - if (typeof initialValue === "boolean") { - return initialValue - } - - if (typeof window !== "undefined" && "matchMedia" in window) { - return window.matchMedia(query).matches - } - - return false -} - -export function useMediaQuery( - query: string, - initialValue?: boolean, - { getInitialValueInEffect }: UseMediaQueryOptions = { - getInitialValueInEffect: true, - } -) { - const [matches, setMatches] = React.useState( - getInitialValueInEffect ? false : getInitialValue(query, initialValue) - ) - const queryRef = React.useRef() - - React.useEffect(() => { - if ("matchMedia" in window) { - queryRef.current = window.matchMedia(query) - setMatches(queryRef.current.matches) - return attachMediaListener(queryRef.current, (event) => - setMatches(event.matches) - ) - } - - return undefined - }, [query]) - - return matches -} diff --git a/apps/www/registry/new-york/ui/use-stepper.tsx b/apps/www/registry/new-york/ui/use-stepper.tsx new file mode 100644 index 00000000000..8da54c632a6 --- /dev/null +++ b/apps/www/registry/new-york/ui/use-stepper.tsx @@ -0,0 +1,59 @@ +import * as React from "react" + +import { useStepperContext } from "./stepper" + +type UseStepper = { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number + isDisabledStep: boolean + isLastStep: boolean + isOptionalStep: boolean | undefined +} + +export function useStepper(): UseStepper { + const { steps, initialStep } = useStepperContext() + + if (steps.length === 0) { + throw new Error( + "useStepper must be used within a StepperProvider. Wrap a parent component in to fix this error." + ) + } + + const [activeStep, setActiveStep] = React.useState(initialStep) + + const nextStep = () => { + setActiveStep((prev) => prev + 1) + } + + const prevStep = () => { + setActiveStep((prev) => prev - 1) + } + + const resetSteps = () => { + setActiveStep(initialStep) + } + + const setStep = (step: number) => { + setActiveStep(step) + } + + const isDisabledStep = activeStep === 0 + + const isLastStep = activeStep === steps.length - 1 + + const isOptionalStep = steps[activeStep]?.optional + + return { + nextStep, + prevStep, + resetSteps, + setStep, + activeStep, + isDisabledStep, + isLastStep, + isOptionalStep, + } +} From ac80464879dd801a87c998ebddd7c37d65bdf0ca Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Tue, 2 Jan 2024 00:35:35 -0300 Subject: [PATCH 10/62] refactor: wip structure --- .../new-york/example/stepper-demo.tsx | 74 ++++--- apps/www/registry/new-york/ui/stepper.tsx | 207 ++++++++++-------- apps/www/registry/new-york/ui/use-stepper.tsx | 52 +---- 3 files changed, 163 insertions(+), 170 deletions(-) diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index edfe851b3f8..b9914d5218f 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -1,31 +1,17 @@ import { Button } from "@/registry/new-york/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] export default function StepperDemo() { - // const { - // nextStep, - // prevStep, - // resetSteps, - // activeStep, - // isDisabledStep, - // isLastStep, - // isOptionalStep, - // } = useStepper() - return (
- + {steps.map((step, index) => (
@@ -33,24 +19,42 @@ export default function StepperDemo() {
))} + + +
- {/*
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
*/} +
+ ) +} + +function MyStepperFooter() { + const { + activeStep, + isLastStep, + isOptionalStep, + isDisabledStep, + nextStep, + prevStep, + resetSteps, + steps, + } = useStepper() + + return ( +
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 5a171cd22bd..92ba031ecc6 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -1,5 +1,6 @@ import React from "react" import { cva } from "class-variance-authority" +import { Check } from "lucide-react" import { cn } from "@/lib/utils" import { useMediaQuery } from "@/hooks/use-media-query" @@ -7,7 +8,22 @@ import { useMediaQuery } from "@/hooks/use-media-query" import { Button } from "./button" import { Separator } from "./separator" -type UseStepper = { +interface StepperProps { + steps: { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + }[] + initialStep: number + orientation?: "vertical" | "horizontal" + responsive?: boolean + labelOrientation?: "vertical" | "horizontal" + variant?: "default" | "ghost" | "outline" | "secondary" + status?: "default" | "success" | "error" | "warning" | "loading" + clickable?: boolean +} + +interface ContextStepperProps extends StepperProps { nextStep: () => void prevStep: () => void resetSteps: () => void @@ -18,17 +34,27 @@ type UseStepper = { isOptionalStep: boolean | undefined } -export function useStepper(): UseStepper { - const { steps, initialStep } = useStepperContext() - - if (steps.length === 0) { - throw new Error( - "useStepper must be used within a StepperProvider. Wrap a parent component in to fix this error." - ) - } - - const [activeStep, setActiveStep] = React.useState(initialStep) +const StepperContext = React.createContext({ + steps: [], + initialStep: 0, + nextStep: () => {}, + prevStep: () => {}, + resetSteps: () => {}, + setStep: () => {}, + activeStep: 0, + isDisabledStep: false, + isLastStep: false, + isOptionalStep: false, +}) +const StepperProvider = ({ + value, + children, +}: { + value: StepperProps + children: React.ReactNode +}) => { + const [activeStep, setActiveStep] = React.useState(value.initialStep) const nextStep = () => { setActiveStep((prev) => prev + 1) } @@ -38,7 +64,7 @@ export function useStepper(): UseStepper { } const resetSteps = () => { - setActiveStep(initialStep) + setActiveStep(value.initialStep) } const setStep = (step: number) => { @@ -47,56 +73,33 @@ export function useStepper(): UseStepper { const isDisabledStep = activeStep === 0 - const isLastStep = activeStep === steps.length - 1 - - const isOptionalStep = steps[activeStep]?.optional + const isLastStep = activeStep === value.steps.length - 1 - return { - nextStep, - prevStep, - resetSteps, - setStep, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } -} + const isOptionalStep = value.steps[activeStep]?.optional -interface StepperProps { - initialStep: number - steps: { - label: string | React.ReactNode - description?: string | React.ReactNode - optional?: boolean - }[] - orientation?: "vertical" | "horizontal" - labelOrientation?: "vertical" | "horizontal" - responsive?: boolean - variant?: "default" | "ghost" | "outline" | "secondary" - status?: "default" | "success" | "error" | "warning" | "loading" - clickable?: boolean -} - -const StepperContext = React.createContext({ - initialStep: 0, - steps: [], -}) - -export const useStepperContext = () => React.useContext(StepperContext) - -const StepperProvider = ({ - value, - children, -}: { - value: StepperProps - children: React.ReactNode -}) => { return ( - {children} + + {children} + ) } +export function useStepper() { + return React.useContext(StepperContext) +} + export const Stepper = React.forwardRef< HTMLDivElement, StepperProps & React.HTMLAttributes @@ -122,15 +125,18 @@ export const Stepper = React.forwardRef< const childrens = React.Children.toArray(children).map((child, index) => { if (!React.isValidElement(child)) { - return null + throw new Error("Stepper children must be valid React elements.") } - if (child.type !== StepperItem) { - return child + if (child.type !== StepperItem && child.type !== StepperFooter) { + throw new Error( + "Stepper children must be either or ." + ) } const stepperItemProps = { ...child.props, step: index, } + return React.cloneElement(child, stepperItemProps) }) @@ -143,24 +149,40 @@ export const Stepper = React.forwardRef< responsive, labelOrientation, variant, + clickable, + status, }} > -
+
+ {childrens} +
+ {orientation === "horizontal" && ( + {children} )} - > - {childrens}
) } ) +const HorizontalContent = ({ children }: { children?: React.ReactNode }) => { + const { activeStep } = useStepper() + + const activeStepperItem = React.Children.toArray(children)[ + activeStep + ] as React.ReactElement + + const content = activeStepperItem?.props?.children + + return content +} + const stepperItemVariants = cva("relative flex flex-row gap-2", { variants: { isLastStep: { @@ -189,14 +211,19 @@ export const StepperItem = React.forwardRef< React.HTMLAttributes & { icon?: React.ReactNode } - // @ts-ignore + // @ts-ignore - step is a prop that is added from the Stepper through React.Children. >(({ step, children, className, onClick, ...props }, ref) => { - const { steps, orientation, labelOrientation, variant, status, clickable } = - useStepperContext() - - const { activeStep, setStep } = useStepper() - - const isLastStep = steps.length - 1 === step + const { + activeStep, + setStep, + steps, + isLastStep, + orientation, + labelOrientation, + variant, + status, + clickable, + } = useStepper() const isActive = step === activeStep const isCompleted = step < activeStep @@ -229,6 +256,7 @@ export const StepperItem = React.forwardRef< ref={ref} onClick={onClickItem} aria-disabled={!isVisited} + {...props} >
- {isCompleted ? ( - - - - ) : ( - step + 1 - )} + {isCompleted ? : step + 1}
+>(({ children, ...props }, ref) => { + return ( +
+ {children} +
+ ) +}) + +StepperFooter.displayName = "StepperFooter" diff --git a/apps/www/registry/new-york/ui/use-stepper.tsx b/apps/www/registry/new-york/ui/use-stepper.tsx index 8da54c632a6..a95dcfa0701 100644 --- a/apps/www/registry/new-york/ui/use-stepper.tsx +++ b/apps/www/registry/new-york/ui/use-stepper.tsx @@ -1,8 +1,11 @@ import * as React from "react" -import { useStepperContext } from "./stepper" - type UseStepper = { + steps: { + label: string | React.ReactNode + description?: string | React.ReactNode + optional?: boolean + }[] nextStep: () => void prevStep: () => void resetSteps: () => void @@ -13,47 +16,6 @@ type UseStepper = { isOptionalStep: boolean | undefined } -export function useStepper(): UseStepper { - const { steps, initialStep } = useStepperContext() - - if (steps.length === 0) { - throw new Error( - "useStepper must be used within a StepperProvider. Wrap a parent component in to fix this error." - ) - } - - const [activeStep, setActiveStep] = React.useState(initialStep) - - const nextStep = () => { - setActiveStep((prev) => prev + 1) - } - - const prevStep = () => { - setActiveStep((prev) => prev - 1) - } - - const resetSteps = () => { - setActiveStep(initialStep) - } - - const setStep = (step: number) => { - setActiveStep(step) - } - - const isDisabledStep = activeStep === 0 - - const isLastStep = activeStep === steps.length - 1 - - const isOptionalStep = steps[activeStep]?.optional - - return { - nextStep, - prevStep, - resetSteps, - setStep, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } +export function useStepper() { + return null } From fc96e31ca4a0ee17223a8d8d884281587cdae9f0 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 8 Jan 2024 00:17:30 -0300 Subject: [PATCH 11/62] feat: improve DX and add smooth scroll in horizontal orientation --- .../new-york/example/stepper-demo.tsx | 23 ++- apps/www/registry/new-york/ui/stepper.tsx | 139 +++++++++++------- 2 files changed, 110 insertions(+), 52 deletions(-) diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index b9914d5218f..63f1fccdc48 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -1,3 +1,6 @@ +import { useEffect, useRef } from "react" +import { Moon } from "lucide-react" + import { Button } from "@/registry/new-york/ui/button" import { Stepper, @@ -6,12 +9,28 @@ import { useStepper, } from "@/registry/new-york/ui/stepper" -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +const steps = [ + { label: "Step 1", description: "Testing" }, + { label: "Step 2" }, + { label: "Step 3" }, + // { label: "Step 4" }, + // { label: "Step 5" }, + // { label: "Step 6" }, + // { label: "Step 7" }, + // { label: "Step 8" }, + // { label: "Step 9" }, + // { label: "Step 10" }, +] export default function StepperDemo() { return (
- + {steps.map((step, index) => (
diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 92ba031ecc6..4ef5b87d62c 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { useEffect, useRef } from "react" import { cva } from "class-variance-authority" import { Check } from "lucide-react" @@ -29,9 +29,6 @@ interface ContextStepperProps extends StepperProps { resetSteps: () => void setStep: (step: number) => void activeStep: number - isDisabledStep: boolean - isLastStep: boolean - isOptionalStep: boolean | undefined } const StepperContext = React.createContext({ @@ -42,9 +39,6 @@ const StepperContext = React.createContext({ resetSteps: () => {}, setStep: () => {}, activeStep: 0, - isDisabledStep: false, - isLastStep: false, - isOptionalStep: false, }) const StepperProvider = ({ @@ -71,12 +65,6 @@ const StepperProvider = ({ setActiveStep(step) } - const isDisabledStep = activeStep === 0 - - const isLastStep = activeStep === value.steps.length - 1 - - const isOptionalStep = value.steps[activeStep]?.optional - return ( {children} @@ -97,7 +82,24 @@ const StepperProvider = ({ } export function useStepper() { - return React.useContext(StepperContext) + const context = React.useContext(StepperContext) + + if (context === undefined) { + throw new Error("useStepper must be used within a StepperProvider") + } + + const isDisabledStep = context.activeStep === 0 + const isLastStep = context.activeStep === context.steps.length - 1 + const isOptionalStep = context.steps[context.activeStep]?.optional + const isFinished = context.activeStep === context.steps.length + + return { + ...context, + isDisabledStep, + isLastStep, + isOptionalStep, + isFinished, + } } export const Stepper = React.forwardRef< @@ -123,7 +125,9 @@ export const Stepper = React.forwardRef< const isMobile = useMediaQuery("(max-width: 43em)") const orientation = isMobile && responsive ? "vertical" : orientationProp - const childrens = React.Children.toArray(children).map((child, index) => { + const footer = [] as React.ReactElement[] + + const items = React.Children.toArray(children).map((child, index) => { if (!React.isValidElement(child)) { throw new Error("Stepper children must be valid React elements.") } @@ -132,6 +136,10 @@ export const Stepper = React.forwardRef< "Stepper children must be either or ." ) } + if (child.type === StepperFooter) { + footer.push(child) + return null + } const stepperItemProps = { ...child.props, step: index, @@ -160,11 +168,12 @@ export const Stepper = React.forwardRef< orientation === "vertical" ? "flex-col" : "flex-row" )} > - {childrens} + {items}
{orientation === "horizontal" && ( {children} )} + {footer}
) @@ -172,7 +181,11 @@ export const Stepper = React.forwardRef< ) const HorizontalContent = ({ children }: { children?: React.ReactNode }) => { - const { activeStep } = useStepper() + const { activeStep, isFinished } = useStepper() + + if (isFinished) { + return null + } const activeStepperItem = React.Children.toArray(children)[ activeStep @@ -183,7 +196,7 @@ const HorizontalContent = ({ children }: { children?: React.ReactNode }) => { return content } -const stepperItemVariants = cva("relative flex flex-row gap-2", { +const stepperItemVariants = cva("relative flex flex-row gap-4", { variants: { isLastStep: { true: "flex-[0_0_auto] justify-end", @@ -193,9 +206,6 @@ const stepperItemVariants = cva("relative flex flex-row gap-2", { true: "flex-col", false: "items-center", }, - isClickable: { - true: "cursor-pointer", - }, }, compoundVariants: [ { @@ -208,16 +218,16 @@ const stepperItemVariants = cva("relative flex flex-row gap-2", { export const StepperItem = React.forwardRef< HTMLDivElement, - React.HTMLAttributes & { + Omit, "onClick"> & { icon?: React.ReactNode + onClick?: (e: React.MouseEvent) => void } // @ts-ignore - step is a prop that is added from the Stepper through React.Children. ->(({ step, children, className, onClick, ...props }, ref) => { +>(({ step, children, className, onClick, icon, ...props }, ref) => { const { activeStep, setStep, steps, - isLastStep, orientation, labelOrientation, variant, @@ -228,14 +238,25 @@ export const StepperItem = React.forwardRef< const isActive = step === activeStep const isCompleted = step < activeStep const isDisabled = step > activeStep && !clickable - const isVisited = isActive || isCompleted + + const isLastStep = step === steps.length - 1 const isVertical = orientation === "vertical" const isVerticalLabel = labelOrientation === "vertical" const isError = isActive && status === "error" - const onClickItem = (e: React.MouseEvent) => { + const buttonRef = useRef(null) + + const content = React.Children.toArray(children).filter( + (child) => React.isValidElement(child) && child.type !== StepperFooter + ) + + const footer = React.Children.toArray(children).filter( + (child) => React.isValidElement(child) && child.type === StepperFooter + )[0] as React.ReactElement + + const onClickItem = (e: React.MouseEvent) => { if (isDisabled) { return } @@ -243,43 +264,53 @@ export const StepperItem = React.forwardRef< onClick?.(e) } + useEffect(() => { + if (!isActive) { + return + } + buttonRef.current?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + }, [isActive]) + + const iconComponent = icon || step + 1 + return (
@@ -293,17 +324,25 @@ export const StepperItem = React.forwardRef< )}
- {!isLastStep && ( - - )} +
+ {!isLastStep && ( + + )} + {isVertical && isActive && ( +
+ {content} + {footer} +
+ )} +
) }) From fe16bc7626dd9cfd8f2048ead4aa9b85994a8400 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 14 Jan 2024 02:05:22 -0300 Subject: [PATCH 12/62] feat: improve DX, update docs and add scroll tracking --- apps/www/__registry__/index.tsx | 1453 ++++++++++++----- apps/www/content/docs/components/stepper.mdx | 295 ++-- .../example/stepper-clickable-steps.tsx | 91 +- .../default/example/stepper-custom-icons.tsx | 91 +- .../stepper-custom-success-error-icon.tsx | 65 - .../registry/default/example/stepper-demo.tsx | 85 +- .../default/example/stepper-description.tsx | 89 +- .../example/stepper-label-orientation.tsx | 89 +- .../example/stepper-optional-steps.tsx | 85 +- .../default/example/stepper-orientation.tsx | 87 +- .../default/example/stepper-states.tsx | 80 - .../default/example/stepper-status.tsx | 95 ++ apps/www/registry/default/ui/stepper.tsx | 620 +++---- .../example/stepper-clickable-steps.tsx | 91 +- .../new-york/example/stepper-custom-icons.tsx | 91 +- .../stepper-custom-success-error-icon.tsx | 65 - .../new-york/example/stepper-demo.tsx | 39 +- .../new-york/example/stepper-description.tsx | 89 +- .../example/stepper-label-orientation.tsx | 89 +- .../example/stepper-optional-steps.tsx | 85 +- .../new-york/example/stepper-orientation.tsx | 87 +- .../new-york/example/stepper-states.tsx | 80 - .../new-york/example/stepper-status.tsx | 95 ++ apps/www/registry/new-york/ui/stepper.tsx | 105 +- apps/www/registry/registry.ts | 10 +- 25 files changed, 2358 insertions(+), 1793 deletions(-) delete mode 100644 apps/www/registry/default/example/stepper-custom-success-error-icon.tsx delete mode 100644 apps/www/registry/default/example/stepper-states.tsx create mode 100644 apps/www/registry/default/example/stepper-status.tsx delete mode 100644 apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx delete mode 100644 apps/www/registry/new-york/example/stepper-states.tsx create mode 100644 apps/www/registry/new-york/example/stepper-status.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index e59281f419f..a7f57a95bac 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -4,15 +4,15 @@ import * as React from "react" export const Index: Record = { - "default": { - "accordion": { + default: { + accordion: { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/accordion")), files: ["registry/default/ui/accordion.tsx"], }, - "alert": { + alert: { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -33,63 +33,63 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/aspect-ratio")), files: ["registry/default/ui/aspect-ratio.tsx"], }, - "avatar": { + avatar: { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/avatar")), files: ["registry/default/ui/avatar.tsx"], }, - "badge": { + badge: { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/badge")), files: ["registry/default/ui/badge.tsx"], }, - "button": { + button: { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/button")), files: ["registry/default/ui/button.tsx"], }, - "calendar": { + calendar: { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/calendar")), files: ["registry/default/ui/calendar.tsx"], }, - "card": { + card: { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/card")), files: ["registry/default/ui/card.tsx"], }, - "carousel": { + carousel: { name: "carousel", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/carousel")), files: ["registry/default/ui/carousel.tsx"], }, - "checkbox": { + checkbox: { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/checkbox")), files: ["registry/default/ui/checkbox.tsx"], }, - "collapsible": { + collapsible: { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/collapsible")), files: ["registry/default/ui/collapsible.tsx"], }, - "command": { + command: { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -103,14 +103,14 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/context-menu")), files: ["registry/default/ui/context-menu.tsx"], }, - "dialog": { + dialog: { name: "dialog", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/dialog")), files: ["registry/default/ui/dialog.tsx"], }, - "drawer": { + drawer: { name: "drawer", type: "components:ui", registryDependencies: undefined, @@ -121,13 +121,15 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/ui/dropdown-menu")), + component: React.lazy( + () => import("@/registry/default/ui/dropdown-menu") + ), files: ["registry/default/ui/dropdown-menu.tsx"], }, - "form": { + form: { name: "form", type: "components:ui", - registryDependencies: ["button","label"], + registryDependencies: ["button", "label"], component: React.lazy(() => import("@/registry/default/ui/form")), files: ["registry/default/ui/form.tsx"], }, @@ -138,21 +140,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/hover-card")), files: ["registry/default/ui/hover-card.tsx"], }, - "input": { + input: { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/input")), files: ["registry/default/ui/input.tsx"], }, - "label": { + label: { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/label")), files: ["registry/default/ui/label.tsx"], }, - "menubar": { + menubar: { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -163,24 +165,26 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/ui/navigation-menu")), + component: React.lazy( + () => import("@/registry/default/ui/navigation-menu") + ), files: ["registry/default/ui/navigation-menu.tsx"], }, - "pagination": { + pagination: { name: "pagination", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/pagination")), files: ["registry/default/ui/pagination.tsx"], }, - "popover": { + popover: { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/popover")), files: ["registry/default/ui/popover.tsx"], }, - "progress": { + progress: { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -194,7 +198,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/radio-group")), files: ["registry/default/ui/radio-group.tsx"], }, - "resizable": { + resizable: { name: "resizable", type: "components:ui", registryDependencies: undefined, @@ -208,91 +212,98 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/scroll-area")), files: ["registry/default/ui/scroll-area.tsx"], }, - "select": { + select: { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/select")), files: ["registry/default/ui/select.tsx"], }, - "separator": { + separator: { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/separator")), files: ["registry/default/ui/separator.tsx"], }, - "sheet": { + sheet: { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sheet")), files: ["registry/default/ui/sheet.tsx"], }, - "skeleton": { + skeleton: { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/skeleton")), files: ["registry/default/ui/skeleton.tsx"], }, - "slider": { + slider: { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/slider")), files: ["registry/default/ui/slider.tsx"], }, - "sonner": { + sonner: { name: "sonner", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sonner")), files: ["registry/default/ui/sonner.tsx"], }, - "stepper": { + stepper: { name: "stepper", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/stepper")), - files: ["registry/default/ui/stepper.tsx","registry/default/ui/use-stepper.ts"], + files: [ + "registry/default/ui/stepper.tsx", + "registry/default/ui/use-stepper.ts", + ], }, - "switch": { + switch: { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/switch")), files: ["registry/default/ui/switch.tsx"], }, - "table": { + table: { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/table")), files: ["registry/default/ui/table.tsx"], }, - "tabs": { + tabs: { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/tabs")), files: ["registry/default/ui/tabs.tsx"], }, - "textarea": { + textarea: { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/textarea")), files: ["registry/default/ui/textarea.tsx"], }, - "toast": { + toast: { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/toast")), - files: ["registry/default/ui/toast.tsx","registry/default/ui/use-toast.ts","registry/default/ui/toaster.tsx"], + files: [ + "registry/default/ui/toast.tsx", + "registry/default/ui/use-toast.ts", + "registry/default/ui/toaster.tsx", + ], }, - "toggle": { + toggle: { name: "toggle", type: "components:ui", registryDependencies: undefined, @@ -306,7 +317,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/toggle-group")), files: ["registry/default/ui/toggle-group.tsx"], }, - "tooltip": { + tooltip: { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -317,991 +328,1261 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy(() => import("@/registry/default/example/accordion-demo")), + component: React.lazy( + () => import("@/registry/default/example/accordion-demo") + ), files: ["registry/default/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/default/example/alert-demo")), + component: React.lazy( + () => import("@/registry/default/example/alert-demo") + ), files: ["registry/default/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/default/example/alert-destructive")), + component: React.lazy( + () => import("@/registry/default/example/alert-destructive") + ), files: ["registry/default/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog","button"], - component: React.lazy(() => import("@/registry/default/example/alert-dialog-demo")), + registryDependencies: ["alert-dialog", "button"], + component: React.lazy( + () => import("@/registry/default/example/alert-dialog-demo") + ), files: ["registry/default/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy(() => import("@/registry/default/example/aspect-ratio-demo")), + component: React.lazy( + () => import("@/registry/default/example/aspect-ratio-demo") + ), files: ["registry/default/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy(() => import("@/registry/default/example/avatar-demo")), + component: React.lazy( + () => import("@/registry/default/example/avatar-demo") + ), files: ["registry/default/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-demo")), + component: React.lazy( + () => import("@/registry/default/example/badge-demo") + ), files: ["registry/default/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-destructive")), + component: React.lazy( + () => import("@/registry/default/example/badge-destructive") + ), files: ["registry/default/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-outline")), + component: React.lazy( + () => import("@/registry/default/example/badge-outline") + ), files: ["registry/default/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/default/example/badge-secondary")), + component: React.lazy( + () => import("@/registry/default/example/badge-secondary") + ), files: ["registry/default/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-demo")), + component: React.lazy( + () => import("@/registry/default/example/button-demo") + ), files: ["registry/default/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-secondary")), + component: React.lazy( + () => import("@/registry/default/example/button-secondary") + ), files: ["registry/default/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-destructive")), + component: React.lazy( + () => import("@/registry/default/example/button-destructive") + ), files: ["registry/default/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-outline")), + component: React.lazy( + () => import("@/registry/default/example/button-outline") + ), files: ["registry/default/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-ghost")), + component: React.lazy( + () => import("@/registry/default/example/button-ghost") + ), files: ["registry/default/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-link")), + component: React.lazy( + () => import("@/registry/default/example/button-link") + ), files: ["registry/default/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-with-icon")), + component: React.lazy( + () => import("@/registry/default/example/button-with-icon") + ), files: ["registry/default/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-loading")), + component: React.lazy( + () => import("@/registry/default/example/button-loading") + ), files: ["registry/default/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-icon")), + component: React.lazy( + () => import("@/registry/default/example/button-icon") + ), files: ["registry/default/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/default/example/button-as-child")), + component: React.lazy( + () => import("@/registry/default/example/button-as-child") + ), files: ["registry/default/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy(() => import("@/registry/default/example/calendar-demo")), + component: React.lazy( + () => import("@/registry/default/example/calendar-demo") + ), files: ["registry/default/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar","form","popover"], - component: React.lazy(() => import("@/registry/default/example/calendar-form")), + registryDependencies: ["calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/default/example/calendar-form") + ), files: ["registry/default/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card","button","switch"], - component: React.lazy(() => import("@/registry/default/example/card-demo")), + registryDependencies: ["card", "button", "switch"], + component: React.lazy( + () => import("@/registry/default/example/card-demo") + ), files: ["registry/default/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button","card","input","label","select"], - component: React.lazy(() => import("@/registry/default/example/card-with-form")), + registryDependencies: ["button", "card", "input", "label", "select"], + component: React.lazy( + () => import("@/registry/default/example/card-with-form") + ), files: ["registry/default/example/card-with-form.tsx"], }, "carousel-demo": { name: "carousel-demo", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/default/example/carousel-demo")), + component: React.lazy( + () => import("@/registry/default/example/carousel-demo") + ), files: ["registry/default/example/carousel-demo.tsx"], }, "carousel-size": { name: "carousel-size", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/default/example/carousel-size")), + component: React.lazy( + () => import("@/registry/default/example/carousel-size") + ), files: ["registry/default/example/carousel-size.tsx"], }, "carousel-spacing": { name: "carousel-spacing", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/default/example/carousel-spacing")), + component: React.lazy( + () => import("@/registry/default/example/carousel-spacing") + ), files: ["registry/default/example/carousel-spacing.tsx"], }, "carousel-orientation": { name: "carousel-orientation", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/default/example/carousel-orientation")), + component: React.lazy( + () => import("@/registry/default/example/carousel-orientation") + ), files: ["registry/default/example/carousel-orientation.tsx"], }, "carousel-api": { name: "carousel-api", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/default/example/carousel-api")), + component: React.lazy( + () => import("@/registry/default/example/carousel-api") + ), files: ["registry/default/example/carousel-api.tsx"], }, "carousel-plugin": { name: "carousel-plugin", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/default/example/carousel-plugin")), + component: React.lazy( + () => import("@/registry/default/example/carousel-plugin") + ), files: ["registry/default/example/carousel-plugin.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/default/example/checkbox-demo")), + component: React.lazy( + () => import("@/registry/default/example/checkbox-demo") + ), files: ["registry/default/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/default/example/checkbox-disabled")), + component: React.lazy( + () => import("@/registry/default/example/checkbox-disabled") + ), files: ["registry/default/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/default/example/checkbox-form-multiple")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/default/example/checkbox-form-multiple") + ), files: ["registry/default/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/default/example/checkbox-form-single")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/default/example/checkbox-form-single") + ), files: ["registry/default/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/default/example/checkbox-with-text")), + component: React.lazy( + () => import("@/registry/default/example/checkbox-with-text") + ), files: ["registry/default/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy(() => import("@/registry/default/example/collapsible-demo")), + component: React.lazy( + () => import("@/registry/default/example/collapsible-demo") + ), files: ["registry/default/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/default/example/combobox-demo")), + component: React.lazy( + () => import("@/registry/default/example/combobox-demo") + ), files: ["registry/default/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command","dropdown-menu","button"], - component: React.lazy(() => import("@/registry/default/example/combobox-dropdown-menu")), + registryDependencies: ["command", "dropdown-menu", "button"], + component: React.lazy( + () => import("@/registry/default/example/combobox-dropdown-menu") + ), files: ["registry/default/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command","form"], - component: React.lazy(() => import("@/registry/default/example/combobox-form")), + registryDependencies: ["command", "form"], + component: React.lazy( + () => import("@/registry/default/example/combobox-form") + ), files: ["registry/default/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox","popover"], - component: React.lazy(() => import("@/registry/default/example/combobox-popover")), + registryDependencies: ["combobox", "popover"], + component: React.lazy( + () => import("@/registry/default/example/combobox-popover") + ), files: ["registry/default/example/combobox-popover.tsx"], }, "combobox-responsive": { name: "combobox-responsive", type: "components:example", - registryDependencies: ["combobox","popover","drawer"], - component: React.lazy(() => import("@/registry/default/example/combobox-responsive")), + registryDependencies: ["combobox", "popover", "drawer"], + component: React.lazy( + () => import("@/registry/default/example/combobox-responsive") + ), files: ["registry/default/example/combobox-responsive.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/default/example/command-demo")), + component: React.lazy( + () => import("@/registry/default/example/command-demo") + ), files: ["registry/default/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command","dialog"], - component: React.lazy(() => import("@/registry/default/example/command-dialog")), + registryDependencies: ["command", "dialog"], + component: React.lazy( + () => import("@/registry/default/example/command-dialog") + ), files: ["registry/default/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy(() => import("@/registry/default/example/context-menu-demo")), + component: React.lazy( + () => import("@/registry/default/example/context-menu-demo") + ), files: ["registry/default/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy(() => import("@/registry/default/example/data-table-demo")), + component: React.lazy( + () => import("@/registry/default/example/data-table-demo") + ), files: ["registry/default/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/default/example/date-picker-demo")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-demo") + ), files: ["registry/default/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button","calendar","form","popover"], - component: React.lazy(() => import("@/registry/default/example/date-picker-form")), + registryDependencies: ["button", "calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-form") + ), files: ["registry/default/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button","calendar","popover","select"], - component: React.lazy(() => import("@/registry/default/example/date-picker-with-presets")), + registryDependencies: ["button", "calendar", "popover", "select"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-with-presets") + ), files: ["registry/default/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/default/example/date-picker-with-range")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/default/example/date-picker-with-range") + ), files: ["registry/default/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy(() => import("@/registry/default/example/dialog-demo")), + component: React.lazy( + () => import("@/registry/default/example/dialog-demo") + ), files: ["registry/default/example/dialog-demo.tsx"], }, "dialog-close-button": { name: "dialog-close-button", type: "components:example", - registryDependencies: ["dialog","button"], - component: React.lazy(() => import("@/registry/default/example/dialog-close-button")), + registryDependencies: ["dialog", "button"], + component: React.lazy( + () => import("@/registry/default/example/dialog-close-button") + ), files: ["registry/default/example/dialog-close-button.tsx"], }, "drawer-demo": { name: "drawer-demo", type: "components:example", registryDependencies: ["drawer"], - component: React.lazy(() => import("@/registry/default/example/drawer-demo")), + component: React.lazy( + () => import("@/registry/default/example/drawer-demo") + ), files: ["registry/default/example/drawer-demo.tsx"], }, "drawer-dialog": { name: "drawer-dialog", type: "components:example", - registryDependencies: ["drawer","dialog"], - component: React.lazy(() => import("@/registry/default/example/drawer-dialog")), + registryDependencies: ["drawer", "dialog"], + component: React.lazy( + () => import("@/registry/default/example/drawer-dialog") + ), files: ["registry/default/example/drawer-dialog.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy(() => import("@/registry/default/example/dropdown-menu-demo")), + component: React.lazy( + () => import("@/registry/default/example/dropdown-menu-demo") + ), files: ["registry/default/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu","checkbox"], - component: React.lazy(() => import("@/registry/default/example/dropdown-menu-checkboxes")), + registryDependencies: ["dropdown-menu", "checkbox"], + component: React.lazy( + () => import("@/registry/default/example/dropdown-menu-checkboxes") + ), files: ["registry/default/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu","radio-group"], - component: React.lazy(() => import("@/registry/default/example/dropdown-menu-radio-group")), + registryDependencies: ["dropdown-menu", "radio-group"], + component: React.lazy( + () => import("@/registry/default/example/dropdown-menu-radio-group") + ), files: ["registry/default/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy(() => import("@/registry/default/example/hover-card-demo")), + component: React.lazy( + () => import("@/registry/default/example/hover-card-demo") + ), files: ["registry/default/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/default/example/input-demo")), + component: React.lazy( + () => import("@/registry/default/example/input-demo") + ), files: ["registry/default/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/default/example/input-disabled")), + component: React.lazy( + () => import("@/registry/default/example/input-disabled") + ), files: ["registry/default/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/default/example/input-file")), + component: React.lazy( + () => import("@/registry/default/example/input-file") + ), files: ["registry/default/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input","button","form"], - component: React.lazy(() => import("@/registry/default/example/input-form")), + registryDependencies: ["input", "button", "form"], + component: React.lazy( + () => import("@/registry/default/example/input-form") + ), files: ["registry/default/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input","button"], - component: React.lazy(() => import("@/registry/default/example/input-with-button")), + registryDependencies: ["input", "button"], + component: React.lazy( + () => import("@/registry/default/example/input-with-button") + ), files: ["registry/default/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/default/example/input-with-label")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/default/example/input-with-label") + ), files: ["registry/default/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/default/example/input-with-text")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/default/example/input-with-text") + ), files: ["registry/default/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy(() => import("@/registry/default/example/label-demo")), + component: React.lazy( + () => import("@/registry/default/example/label-demo") + ), files: ["registry/default/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy(() => import("@/registry/default/example/menubar-demo")), + component: React.lazy( + () => import("@/registry/default/example/menubar-demo") + ), files: ["registry/default/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy(() => import("@/registry/default/example/navigation-menu-demo")), + component: React.lazy( + () => import("@/registry/default/example/navigation-menu-demo") + ), files: ["registry/default/example/navigation-menu-demo.tsx"], }, "pagination-demo": { name: "pagination-demo", type: "components:example", registryDependencies: ["pagination"], - component: React.lazy(() => import("@/registry/default/example/pagination-demo")), + component: React.lazy( + () => import("@/registry/default/example/pagination-demo") + ), files: ["registry/default/example/pagination-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy(() => import("@/registry/default/example/popover-demo")), + component: React.lazy( + () => import("@/registry/default/example/popover-demo") + ), files: ["registry/default/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy(() => import("@/registry/default/example/progress-demo")), + component: React.lazy( + () => import("@/registry/default/example/progress-demo") + ), files: ["registry/default/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy(() => import("@/registry/default/example/radio-group-demo")), + component: React.lazy( + () => import("@/registry/default/example/radio-group-demo") + ), files: ["registry/default/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group","form"], - component: React.lazy(() => import("@/registry/default/example/radio-group-form")), + registryDependencies: ["radio-group", "form"], + component: React.lazy( + () => import("@/registry/default/example/radio-group-form") + ), files: ["registry/default/example/radio-group-form.tsx"], }, "resizable-demo": { name: "resizable-demo", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/default/example/resizable-demo")), + component: React.lazy( + () => import("@/registry/default/example/resizable-demo") + ), files: ["registry/default/example/resizable-demo.tsx"], }, "resizable-demo-with-handle": { name: "resizable-demo-with-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/default/example/resizable-demo-with-handle")), + component: React.lazy( + () => import("@/registry/default/example/resizable-demo-with-handle") + ), files: ["registry/default/example/resizable-demo-with-handle.tsx"], }, "resizable-vertical": { name: "resizable-vertical", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/default/example/resizable-vertical")), + component: React.lazy( + () => import("@/registry/default/example/resizable-vertical") + ), files: ["registry/default/example/resizable-vertical.tsx"], }, "resizable-handle": { name: "resizable-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/default/example/resizable-handle")), + component: React.lazy( + () => import("@/registry/default/example/resizable-handle") + ), files: ["registry/default/example/resizable-handle.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy(() => import("@/registry/default/example/scroll-area-demo")), + component: React.lazy( + () => import("@/registry/default/example/scroll-area-demo") + ), files: ["registry/default/example/scroll-area-demo.tsx"], }, "scroll-area-horizontal-demo": { name: "scroll-area-horizontal-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy(() => import("@/registry/default/example/scroll-area-horizontal-demo")), + component: React.lazy( + () => import("@/registry/default/example/scroll-area-horizontal-demo") + ), files: ["registry/default/example/scroll-area-horizontal-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/default/example/select-demo")), + component: React.lazy( + () => import("@/registry/default/example/select-demo") + ), files: ["registry/default/example/select-demo.tsx"], }, "select-scrollable": { name: "select-scrollable", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/default/example/select-scrollable")), + component: React.lazy( + () => import("@/registry/default/example/select-scrollable") + ), files: ["registry/default/example/select-scrollable.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/default/example/select-form")), + component: React.lazy( + () => import("@/registry/default/example/select-form") + ), files: ["registry/default/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy(() => import("@/registry/default/example/separator-demo")), + component: React.lazy( + () => import("@/registry/default/example/separator-demo") + ), files: ["registry/default/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/default/example/sheet-demo")), + component: React.lazy( + () => import("@/registry/default/example/sheet-demo") + ), files: ["registry/default/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/default/example/sheet-side")), + component: React.lazy( + () => import("@/registry/default/example/sheet-side") + ), files: ["registry/default/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy(() => import("@/registry/default/example/skeleton-demo")), + component: React.lazy( + () => import("@/registry/default/example/skeleton-demo") + ), files: ["registry/default/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy(() => import("@/registry/default/example/slider-demo")), + component: React.lazy( + () => import("@/registry/default/example/slider-demo") + ), files: ["registry/default/example/slider-demo.tsx"], }, "sonner-demo": { name: "sonner-demo", type: "components:example", registryDependencies: ["sonner"], - component: React.lazy(() => import("@/registry/default/example/sonner-demo")), + component: React.lazy( + () => import("@/registry/default/example/sonner-demo") + ), files: ["registry/default/example/sonner-demo.tsx"], }, "stepper-demo": { name: "stepper-demo", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-demo")), + component: React.lazy( + () => import("@/registry/default/example/stepper-demo") + ), files: ["registry/default/example/stepper-demo.tsx"], }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-orientation")), + component: React.lazy( + () => import("@/registry/default/example/stepper-orientation") + ), files: ["registry/default/example/stepper-orientation.tsx"], }, "stepper-description": { name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-description")), + component: React.lazy( + () => import("@/registry/default/example/stepper-description") + ), files: ["registry/default/example/stepper-description.tsx"], }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-custom-icons")), + component: React.lazy( + () => import("@/registry/default/example/stepper-custom-icons") + ), files: ["registry/default/example/stepper-custom-icons.tsx"], }, - "stepper-custom-success-error-icon": { - name: "stepper-custom-success-error-icon", - type: "components:example", - registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-custom-success-error-icon")), - files: ["registry/default/example/stepper-custom-success-error-icon.tsx"], - }, "stepper-label-orientation": { name: "stepper-label-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-label-orientation")), + component: React.lazy( + () => import("@/registry/default/example/stepper-label-orientation") + ), files: ["registry/default/example/stepper-label-orientation.tsx"], }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-clickable-steps")), + component: React.lazy( + () => import("@/registry/default/example/stepper-clickable-steps") + ), files: ["registry/default/example/stepper-clickable-steps.tsx"], }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-optional-steps")), + component: React.lazy( + () => import("@/registry/default/example/stepper-optional-steps") + ), files: ["registry/default/example/stepper-optional-steps.tsx"], }, - "stepper-states": { - name: "stepper-states", + "stepper-status": { + name: "stepper-status", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-states")), - files: ["registry/default/example/stepper-states.tsx"], + component: React.lazy( + () => import("@/registry/default/example/stepper-status") + ), + files: ["registry/default/example/stepper-status.tsx"], }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy(() => import("@/registry/default/example/switch-demo")), + component: React.lazy( + () => import("@/registry/default/example/switch-demo") + ), files: ["registry/default/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch","form"], - component: React.lazy(() => import("@/registry/default/example/switch-form")), + registryDependencies: ["switch", "form"], + component: React.lazy( + () => import("@/registry/default/example/switch-form") + ), files: ["registry/default/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy(() => import("@/registry/default/example/table-demo")), + component: React.lazy( + () => import("@/registry/default/example/table-demo") + ), files: ["registry/default/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy(() => import("@/registry/default/example/tabs-demo")), + component: React.lazy( + () => import("@/registry/default/example/tabs-demo") + ), files: ["registry/default/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/default/example/textarea-demo")), + component: React.lazy( + () => import("@/registry/default/example/textarea-demo") + ), files: ["registry/default/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/default/example/textarea-disabled")), + component: React.lazy( + () => import("@/registry/default/example/textarea-disabled") + ), files: ["registry/default/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea","form"], - component: React.lazy(() => import("@/registry/default/example/textarea-form")), + registryDependencies: ["textarea", "form"], + component: React.lazy( + () => import("@/registry/default/example/textarea-form") + ), files: ["registry/default/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea","button"], - component: React.lazy(() => import("@/registry/default/example/textarea-with-button")), + registryDependencies: ["textarea", "button"], + component: React.lazy( + () => import("@/registry/default/example/textarea-with-button") + ), files: ["registry/default/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/default/example/textarea-with-label")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/default/example/textarea-with-label") + ), files: ["registry/default/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/default/example/textarea-with-text")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/default/example/textarea-with-text") + ), files: ["registry/default/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-demo")), + component: React.lazy( + () => import("@/registry/default/example/toast-demo") + ), files: ["registry/default/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-destructive")), + component: React.lazy( + () => import("@/registry/default/example/toast-destructive") + ), files: ["registry/default/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-simple")), + component: React.lazy( + () => import("@/registry/default/example/toast-simple") + ), files: ["registry/default/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-with-action")), + component: React.lazy( + () => import("@/registry/default/example/toast-with-action") + ), files: ["registry/default/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/default/example/toast-with-title")), + component: React.lazy( + () => import("@/registry/default/example/toast-with-title") + ), files: ["registry/default/example/toast-with-title.tsx"], }, "toggle-group-demo": { name: "toggle-group-demo", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/default/example/toggle-group-demo")), + component: React.lazy( + () => import("@/registry/default/example/toggle-group-demo") + ), files: ["registry/default/example/toggle-group-demo.tsx"], }, "toggle-group-disabled": { name: "toggle-group-disabled", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/default/example/toggle-group-disabled")), + component: React.lazy( + () => import("@/registry/default/example/toggle-group-disabled") + ), files: ["registry/default/example/toggle-group-disabled.tsx"], }, "toggle-group-lg": { name: "toggle-group-lg", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/default/example/toggle-group-lg")), + component: React.lazy( + () => import("@/registry/default/example/toggle-group-lg") + ), files: ["registry/default/example/toggle-group-lg.tsx"], }, "toggle-group-outline": { name: "toggle-group-outline", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/default/example/toggle-group-outline")), + component: React.lazy( + () => import("@/registry/default/example/toggle-group-outline") + ), files: ["registry/default/example/toggle-group-outline.tsx"], }, "toggle-group-sm": { name: "toggle-group-sm", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/default/example/toggle-group-sm")), + component: React.lazy( + () => import("@/registry/default/example/toggle-group-sm") + ), files: ["registry/default/example/toggle-group-sm.tsx"], }, "toggle-group-single": { name: "toggle-group-single", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/default/example/toggle-group-single")), + component: React.lazy( + () => import("@/registry/default/example/toggle-group-single") + ), files: ["registry/default/example/toggle-group-single.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-demo")), + component: React.lazy( + () => import("@/registry/default/example/toggle-demo") + ), files: ["registry/default/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-disabled")), + component: React.lazy( + () => import("@/registry/default/example/toggle-disabled") + ), files: ["registry/default/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-lg")), + component: React.lazy( + () => import("@/registry/default/example/toggle-lg") + ), files: ["registry/default/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-outline")), + component: React.lazy( + () => import("@/registry/default/example/toggle-outline") + ), files: ["registry/default/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-sm")), + component: React.lazy( + () => import("@/registry/default/example/toggle-sm") + ), files: ["registry/default/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/default/example/toggle-with-text")), + component: React.lazy( + () => import("@/registry/default/example/toggle-with-text") + ), files: ["registry/default/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy(() => import("@/registry/default/example/tooltip-demo")), + component: React.lazy( + () => import("@/registry/default/example/tooltip-demo") + ), files: ["registry/default/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-blockquote")), + component: React.lazy( + () => import("@/registry/default/example/typography-blockquote") + ), files: ["registry/default/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-demo")), + component: React.lazy( + () => import("@/registry/default/example/typography-demo") + ), files: ["registry/default/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h1")), + component: React.lazy( + () => import("@/registry/default/example/typography-h1") + ), files: ["registry/default/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h2")), + component: React.lazy( + () => import("@/registry/default/example/typography-h2") + ), files: ["registry/default/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h3")), + component: React.lazy( + () => import("@/registry/default/example/typography-h3") + ), files: ["registry/default/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-h4")), + component: React.lazy( + () => import("@/registry/default/example/typography-h4") + ), files: ["registry/default/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-inline-code")), + component: React.lazy( + () => import("@/registry/default/example/typography-inline-code") + ), files: ["registry/default/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-large")), + component: React.lazy( + () => import("@/registry/default/example/typography-large") + ), files: ["registry/default/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-lead")), + component: React.lazy( + () => import("@/registry/default/example/typography-lead") + ), files: ["registry/default/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-list")), + component: React.lazy( + () => import("@/registry/default/example/typography-list") + ), files: ["registry/default/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-muted")), + component: React.lazy( + () => import("@/registry/default/example/typography-muted") + ), files: ["registry/default/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-p")), + component: React.lazy( + () => import("@/registry/default/example/typography-p") + ), files: ["registry/default/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-small")), + component: React.lazy( + () => import("@/registry/default/example/typography-small") + ), files: ["registry/default/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/typography-table")), + component: React.lazy( + () => import("@/registry/default/example/typography-table") + ), files: ["registry/default/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/default/example/mode-toggle")), + component: React.lazy( + () => import("@/registry/default/example/mode-toggle") + ), files: ["registry/default/example/mode-toggle.tsx"], }, - "cards": { + cards: { name: "cards", type: "components:example", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/example/cards")), files: ["registry/default/example/cards/cards.tsx"], }, - }, "new-york": { - "accordion": { + }, + "new-york": { + accordion: { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/accordion")), files: ["registry/new-york/ui/accordion.tsx"], }, - "alert": { + alert: { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -1312,73 +1593,77 @@ export const Index: Record = { name: "alert-dialog", type: "components:ui", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/ui/alert-dialog")), + component: React.lazy( + () => import("@/registry/new-york/ui/alert-dialog") + ), files: ["registry/new-york/ui/alert-dialog.tsx"], }, "aspect-ratio": { name: "aspect-ratio", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/aspect-ratio")), + component: React.lazy( + () => import("@/registry/new-york/ui/aspect-ratio") + ), files: ["registry/new-york/ui/aspect-ratio.tsx"], }, - "avatar": { + avatar: { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/avatar")), files: ["registry/new-york/ui/avatar.tsx"], }, - "badge": { + badge: { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/badge")), files: ["registry/new-york/ui/badge.tsx"], }, - "button": { + button: { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/button")), files: ["registry/new-york/ui/button.tsx"], }, - "calendar": { + calendar: { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/calendar")), files: ["registry/new-york/ui/calendar.tsx"], }, - "card": { + card: { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/card")), files: ["registry/new-york/ui/card.tsx"], }, - "carousel": { + carousel: { name: "carousel", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/carousel")), files: ["registry/new-york/ui/carousel.tsx"], }, - "checkbox": { + checkbox: { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/checkbox")), files: ["registry/new-york/ui/checkbox.tsx"], }, - "collapsible": { + collapsible: { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/collapsible")), files: ["registry/new-york/ui/collapsible.tsx"], }, - "command": { + command: { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -1389,17 +1674,19 @@ export const Index: Record = { name: "context-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/context-menu")), + component: React.lazy( + () => import("@/registry/new-york/ui/context-menu") + ), files: ["registry/new-york/ui/context-menu.tsx"], }, - "dialog": { + dialog: { name: "dialog", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/dialog")), files: ["registry/new-york/ui/dialog.tsx"], }, - "drawer": { + drawer: { name: "drawer", type: "components:ui", registryDependencies: undefined, @@ -1410,13 +1697,15 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/dropdown-menu")), + component: React.lazy( + () => import("@/registry/new-york/ui/dropdown-menu") + ), files: ["registry/new-york/ui/dropdown-menu.tsx"], }, - "form": { + form: { name: "form", type: "components:ui", - registryDependencies: ["button","label"], + registryDependencies: ["button", "label"], component: React.lazy(() => import("@/registry/new-york/ui/form")), files: ["registry/new-york/ui/form.tsx"], }, @@ -1427,21 +1716,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/hover-card")), files: ["registry/new-york/ui/hover-card.tsx"], }, - "input": { + input: { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/input")), files: ["registry/new-york/ui/input.tsx"], }, - "label": { + label: { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/label")), files: ["registry/new-york/ui/label.tsx"], }, - "menubar": { + menubar: { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -1452,24 +1741,26 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/ui/navigation-menu")), + component: React.lazy( + () => import("@/registry/new-york/ui/navigation-menu") + ), files: ["registry/new-york/ui/navigation-menu.tsx"], }, - "pagination": { + pagination: { name: "pagination", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/pagination")), files: ["registry/new-york/ui/pagination.tsx"], }, - "popover": { + popover: { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/popover")), files: ["registry/new-york/ui/popover.tsx"], }, - "progress": { + progress: { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -1483,7 +1774,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/radio-group")), files: ["registry/new-york/ui/radio-group.tsx"], }, - "resizable": { + resizable: { name: "resizable", type: "components:ui", registryDependencies: undefined, @@ -1497,91 +1788,98 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/scroll-area")), files: ["registry/new-york/ui/scroll-area.tsx"], }, - "select": { + select: { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/select")), files: ["registry/new-york/ui/select.tsx"], }, - "separator": { + separator: { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/separator")), files: ["registry/new-york/ui/separator.tsx"], }, - "sheet": { + sheet: { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sheet")), files: ["registry/new-york/ui/sheet.tsx"], }, - "skeleton": { + skeleton: { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/skeleton")), files: ["registry/new-york/ui/skeleton.tsx"], }, - "slider": { + slider: { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/slider")), files: ["registry/new-york/ui/slider.tsx"], }, - "sonner": { + sonner: { name: "sonner", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sonner")), files: ["registry/new-york/ui/sonner.tsx"], }, - "stepper": { + stepper: { name: "stepper", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/stepper")), - files: ["registry/new-york/ui/stepper.tsx","registry/new-york/ui/use-stepper.ts"], + files: [ + "registry/new-york/ui/stepper.tsx", + "registry/new-york/ui/use-stepper.ts", + ], }, - "switch": { + switch: { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/switch")), files: ["registry/new-york/ui/switch.tsx"], }, - "table": { + table: { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/table")), files: ["registry/new-york/ui/table.tsx"], }, - "tabs": { + tabs: { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/tabs")), files: ["registry/new-york/ui/tabs.tsx"], }, - "textarea": { + textarea: { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/textarea")), files: ["registry/new-york/ui/textarea.tsx"], }, - "toast": { + toast: { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/toast")), - files: ["registry/new-york/ui/toast.tsx","registry/new-york/ui/use-toast.ts","registry/new-york/ui/toaster.tsx"], + files: [ + "registry/new-york/ui/toast.tsx", + "registry/new-york/ui/use-toast.ts", + "registry/new-york/ui/toaster.tsx", + ], }, - "toggle": { + toggle: { name: "toggle", type: "components:ui", registryDependencies: undefined, @@ -1592,10 +1890,12 @@ export const Index: Record = { name: "toggle-group", type: "components:ui", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/ui/toggle-group")), + component: React.lazy( + () => import("@/registry/new-york/ui/toggle-group") + ), files: ["registry/new-york/ui/toggle-group.tsx"], }, - "tooltip": { + tooltip: { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -1606,976 +1906,1245 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy(() => import("@/registry/new-york/example/accordion-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/accordion-demo") + ), files: ["registry/new-york/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/new-york/example/alert-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/alert-demo") + ), files: ["registry/new-york/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy(() => import("@/registry/new-york/example/alert-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/alert-destructive") + ), files: ["registry/new-york/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog","button"], - component: React.lazy(() => import("@/registry/new-york/example/alert-dialog-demo")), + registryDependencies: ["alert-dialog", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/alert-dialog-demo") + ), files: ["registry/new-york/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy(() => import("@/registry/new-york/example/aspect-ratio-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/aspect-ratio-demo") + ), files: ["registry/new-york/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy(() => import("@/registry/new-york/example/avatar-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/avatar-demo") + ), files: ["registry/new-york/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-demo") + ), files: ["registry/new-york/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-destructive") + ), files: ["registry/new-york/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-outline")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-outline") + ), files: ["registry/new-york/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy(() => import("@/registry/new-york/example/badge-secondary")), + component: React.lazy( + () => import("@/registry/new-york/example/badge-secondary") + ), files: ["registry/new-york/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/button-demo") + ), files: ["registry/new-york/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-secondary")), + component: React.lazy( + () => import("@/registry/new-york/example/button-secondary") + ), files: ["registry/new-york/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/button-destructive") + ), files: ["registry/new-york/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-outline")), + component: React.lazy( + () => import("@/registry/new-york/example/button-outline") + ), files: ["registry/new-york/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-ghost")), + component: React.lazy( + () => import("@/registry/new-york/example/button-ghost") + ), files: ["registry/new-york/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-link")), + component: React.lazy( + () => import("@/registry/new-york/example/button-link") + ), files: ["registry/new-york/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-with-icon")), + component: React.lazy( + () => import("@/registry/new-york/example/button-with-icon") + ), files: ["registry/new-york/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-loading")), + component: React.lazy( + () => import("@/registry/new-york/example/button-loading") + ), files: ["registry/new-york/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-icon")), + component: React.lazy( + () => import("@/registry/new-york/example/button-icon") + ), files: ["registry/new-york/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy(() => import("@/registry/new-york/example/button-as-child")), + component: React.lazy( + () => import("@/registry/new-york/example/button-as-child") + ), files: ["registry/new-york/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy(() => import("@/registry/new-york/example/calendar-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/calendar-demo") + ), files: ["registry/new-york/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar","form","popover"], - component: React.lazy(() => import("@/registry/new-york/example/calendar-form")), + registryDependencies: ["calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/calendar-form") + ), files: ["registry/new-york/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card","button","switch"], - component: React.lazy(() => import("@/registry/new-york/example/card-demo")), + registryDependencies: ["card", "button", "switch"], + component: React.lazy( + () => import("@/registry/new-york/example/card-demo") + ), files: ["registry/new-york/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button","card","input","label","select"], - component: React.lazy(() => import("@/registry/new-york/example/card-with-form")), + registryDependencies: ["button", "card", "input", "label", "select"], + component: React.lazy( + () => import("@/registry/new-york/example/card-with-form") + ), files: ["registry/new-york/example/card-with-form.tsx"], }, "carousel-demo": { name: "carousel-demo", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/new-york/example/carousel-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/carousel-demo") + ), files: ["registry/new-york/example/carousel-demo.tsx"], }, "carousel-size": { name: "carousel-size", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/new-york/example/carousel-size")), + component: React.lazy( + () => import("@/registry/new-york/example/carousel-size") + ), files: ["registry/new-york/example/carousel-size.tsx"], }, "carousel-spacing": { name: "carousel-spacing", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/new-york/example/carousel-spacing")), + component: React.lazy( + () => import("@/registry/new-york/example/carousel-spacing") + ), files: ["registry/new-york/example/carousel-spacing.tsx"], }, "carousel-orientation": { name: "carousel-orientation", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/new-york/example/carousel-orientation")), + component: React.lazy( + () => import("@/registry/new-york/example/carousel-orientation") + ), files: ["registry/new-york/example/carousel-orientation.tsx"], }, "carousel-api": { name: "carousel-api", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/new-york/example/carousel-api")), + component: React.lazy( + () => import("@/registry/new-york/example/carousel-api") + ), files: ["registry/new-york/example/carousel-api.tsx"], }, "carousel-plugin": { name: "carousel-plugin", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy(() => import("@/registry/new-york/example/carousel-plugin")), + component: React.lazy( + () => import("@/registry/new-york/example/carousel-plugin") + ), files: ["registry/new-york/example/carousel-plugin.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-demo") + ), files: ["registry/new-york/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-disabled") + ), files: ["registry/new-york/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-multiple")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-form-multiple") + ), files: ["registry/new-york/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox","form"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-single")), + registryDependencies: ["checkbox", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-form-single") + ), files: ["registry/new-york/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/checkbox-with-text")), + component: React.lazy( + () => import("@/registry/new-york/example/checkbox-with-text") + ), files: ["registry/new-york/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy(() => import("@/registry/new-york/example/collapsible-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/collapsible-demo") + ), files: ["registry/new-york/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/combobox-demo") + ), files: ["registry/new-york/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command","dropdown-menu","button"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-dropdown-menu")), + registryDependencies: ["command", "dropdown-menu", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/combobox-dropdown-menu") + ), files: ["registry/new-york/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command","form"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-form")), + registryDependencies: ["command", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/combobox-form") + ), files: ["registry/new-york/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox","popover"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-popover")), + registryDependencies: ["combobox", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/combobox-popover") + ), files: ["registry/new-york/example/combobox-popover.tsx"], }, "combobox-responsive": { name: "combobox-responsive", type: "components:example", - registryDependencies: ["combobox","popover","drawer"], - component: React.lazy(() => import("@/registry/new-york/example/combobox-responsive")), + registryDependencies: ["combobox", "popover", "drawer"], + component: React.lazy( + () => import("@/registry/new-york/example/combobox-responsive") + ), files: ["registry/new-york/example/combobox-responsive.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy(() => import("@/registry/new-york/example/command-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/command-demo") + ), files: ["registry/new-york/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command","dialog"], - component: React.lazy(() => import("@/registry/new-york/example/command-dialog")), + registryDependencies: ["command", "dialog"], + component: React.lazy( + () => import("@/registry/new-york/example/command-dialog") + ), files: ["registry/new-york/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy(() => import("@/registry/new-york/example/context-menu-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/context-menu-demo") + ), files: ["registry/new-york/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy(() => import("@/registry/new-york/example/data-table-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/data-table-demo") + ), files: ["registry/new-york/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-demo")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-demo") + ), files: ["registry/new-york/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button","calendar","form","popover"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-form")), + registryDependencies: ["button", "calendar", "form", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-form") + ), files: ["registry/new-york/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button","calendar","popover","select"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-presets")), + registryDependencies: ["button", "calendar", "popover", "select"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-with-presets") + ), files: ["registry/new-york/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button","calendar","popover"], - component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-range")), + registryDependencies: ["button", "calendar", "popover"], + component: React.lazy( + () => import("@/registry/new-york/example/date-picker-with-range") + ), files: ["registry/new-york/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy(() => import("@/registry/new-york/example/dialog-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/dialog-demo") + ), files: ["registry/new-york/example/dialog-demo.tsx"], }, "dialog-close-button": { name: "dialog-close-button", type: "components:example", - registryDependencies: ["dialog","button"], - component: React.lazy(() => import("@/registry/new-york/example/dialog-close-button")), + registryDependencies: ["dialog", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/dialog-close-button") + ), files: ["registry/new-york/example/dialog-close-button.tsx"], }, "drawer-demo": { name: "drawer-demo", type: "components:example", registryDependencies: ["drawer"], - component: React.lazy(() => import("@/registry/new-york/example/drawer-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/drawer-demo") + ), files: ["registry/new-york/example/drawer-demo.tsx"], }, "drawer-dialog": { name: "drawer-dialog", type: "components:example", - registryDependencies: ["drawer","dialog"], - component: React.lazy(() => import("@/registry/new-york/example/drawer-dialog")), + registryDependencies: ["drawer", "dialog"], + component: React.lazy( + () => import("@/registry/new-york/example/drawer-dialog") + ), files: ["registry/new-york/example/drawer-dialog.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/dropdown-menu-demo") + ), files: ["registry/new-york/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu","checkbox"], - component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-checkboxes")), + registryDependencies: ["dropdown-menu", "checkbox"], + component: React.lazy( + () => import("@/registry/new-york/example/dropdown-menu-checkboxes") + ), files: ["registry/new-york/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu","radio-group"], - component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-radio-group")), + registryDependencies: ["dropdown-menu", "radio-group"], + component: React.lazy( + () => import("@/registry/new-york/example/dropdown-menu-radio-group") + ), files: ["registry/new-york/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy(() => import("@/registry/new-york/example/hover-card-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/hover-card-demo") + ), files: ["registry/new-york/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/new-york/example/input-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/input-demo") + ), files: ["registry/new-york/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/new-york/example/input-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/input-disabled") + ), files: ["registry/new-york/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy(() => import("@/registry/new-york/example/input-file")), + component: React.lazy( + () => import("@/registry/new-york/example/input-file") + ), files: ["registry/new-york/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input","button","form"], - component: React.lazy(() => import("@/registry/new-york/example/input-form")), + registryDependencies: ["input", "button", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/input-form") + ), files: ["registry/new-york/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input","button"], - component: React.lazy(() => import("@/registry/new-york/example/input-with-button")), + registryDependencies: ["input", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/input-with-button") + ), files: ["registry/new-york/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/new-york/example/input-with-label")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/input-with-label") + ), files: ["registry/new-york/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input","button","label"], - component: React.lazy(() => import("@/registry/new-york/example/input-with-text")), + registryDependencies: ["input", "button", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/input-with-text") + ), files: ["registry/new-york/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy(() => import("@/registry/new-york/example/label-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/label-demo") + ), files: ["registry/new-york/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy(() => import("@/registry/new-york/example/menubar-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/menubar-demo") + ), files: ["registry/new-york/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy(() => import("@/registry/new-york/example/navigation-menu-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/navigation-menu-demo") + ), files: ["registry/new-york/example/navigation-menu-demo.tsx"], }, "pagination-demo": { name: "pagination-demo", type: "components:example", registryDependencies: ["pagination"], - component: React.lazy(() => import("@/registry/new-york/example/pagination-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/pagination-demo") + ), files: ["registry/new-york/example/pagination-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy(() => import("@/registry/new-york/example/popover-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/popover-demo") + ), files: ["registry/new-york/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy(() => import("@/registry/new-york/example/progress-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/progress-demo") + ), files: ["registry/new-york/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy(() => import("@/registry/new-york/example/radio-group-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/radio-group-demo") + ), files: ["registry/new-york/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group","form"], - component: React.lazy(() => import("@/registry/new-york/example/radio-group-form")), + registryDependencies: ["radio-group", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/radio-group-form") + ), files: ["registry/new-york/example/radio-group-form.tsx"], }, "resizable-demo": { name: "resizable-demo", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/new-york/example/resizable-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/resizable-demo") + ), files: ["registry/new-york/example/resizable-demo.tsx"], }, "resizable-demo-with-handle": { name: "resizable-demo-with-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/new-york/example/resizable-demo-with-handle")), + component: React.lazy( + () => import("@/registry/new-york/example/resizable-demo-with-handle") + ), files: ["registry/new-york/example/resizable-demo-with-handle.tsx"], }, "resizable-vertical": { name: "resizable-vertical", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/new-york/example/resizable-vertical")), + component: React.lazy( + () => import("@/registry/new-york/example/resizable-vertical") + ), files: ["registry/new-york/example/resizable-vertical.tsx"], }, "resizable-handle": { name: "resizable-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy(() => import("@/registry/new-york/example/resizable-handle")), + component: React.lazy( + () => import("@/registry/new-york/example/resizable-handle") + ), files: ["registry/new-york/example/resizable-handle.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy(() => import("@/registry/new-york/example/scroll-area-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/scroll-area-demo") + ), files: ["registry/new-york/example/scroll-area-demo.tsx"], }, "scroll-area-horizontal-demo": { name: "scroll-area-horizontal-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy(() => import("@/registry/new-york/example/scroll-area-horizontal-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/scroll-area-horizontal-demo") + ), files: ["registry/new-york/example/scroll-area-horizontal-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/new-york/example/select-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/select-demo") + ), files: ["registry/new-york/example/select-demo.tsx"], }, "select-scrollable": { name: "select-scrollable", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/new-york/example/select-scrollable")), + component: React.lazy( + () => import("@/registry/new-york/example/select-scrollable") + ), files: ["registry/new-york/example/select-scrollable.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy(() => import("@/registry/new-york/example/select-form")), + component: React.lazy( + () => import("@/registry/new-york/example/select-form") + ), files: ["registry/new-york/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy(() => import("@/registry/new-york/example/separator-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/separator-demo") + ), files: ["registry/new-york/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/new-york/example/sheet-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/sheet-demo") + ), files: ["registry/new-york/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy(() => import("@/registry/new-york/example/sheet-side")), + component: React.lazy( + () => import("@/registry/new-york/example/sheet-side") + ), files: ["registry/new-york/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy(() => import("@/registry/new-york/example/skeleton-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/skeleton-demo") + ), files: ["registry/new-york/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy(() => import("@/registry/new-york/example/slider-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/slider-demo") + ), files: ["registry/new-york/example/slider-demo.tsx"], }, "sonner-demo": { name: "sonner-demo", type: "components:example", registryDependencies: ["sonner"], - component: React.lazy(() => import("@/registry/new-york/example/sonner-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/sonner-demo") + ), files: ["registry/new-york/example/sonner-demo.tsx"], }, "stepper-demo": { name: "stepper-demo", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/stepper-demo") + ), files: ["registry/new-york/example/stepper-demo.tsx"], }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-orientation")), + component: React.lazy( + () => import("@/registry/new-york/example/stepper-orientation") + ), files: ["registry/new-york/example/stepper-orientation.tsx"], }, "stepper-description": { name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-description")), + component: React.lazy( + () => import("@/registry/new-york/example/stepper-description") + ), files: ["registry/new-york/example/stepper-description.tsx"], }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-icons")), + component: React.lazy( + () => import("@/registry/new-york/example/stepper-custom-icons") + ), files: ["registry/new-york/example/stepper-custom-icons.tsx"], }, - "stepper-custom-success-error-icon": { - name: "stepper-custom-success-error-icon", - type: "components:example", - registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-success-error-icon")), - files: ["registry/new-york/example/stepper-custom-success-error-icon.tsx"], - }, "stepper-label-orientation": { name: "stepper-label-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-label-orientation")), + component: React.lazy( + () => import("@/registry/new-york/example/stepper-label-orientation") + ), files: ["registry/new-york/example/stepper-label-orientation.tsx"], }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-clickable-steps")), + component: React.lazy( + () => import("@/registry/new-york/example/stepper-clickable-steps") + ), files: ["registry/new-york/example/stepper-clickable-steps.tsx"], }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-optional-steps")), + component: React.lazy( + () => import("@/registry/new-york/example/stepper-optional-steps") + ), files: ["registry/new-york/example/stepper-optional-steps.tsx"], }, - "stepper-states": { - name: "stepper-states", + "stepper-status": { + name: "stepper-status", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-states")), - files: ["registry/new-york/example/stepper-states.tsx"], + component: React.lazy( + () => import("@/registry/new-york/example/stepper-status") + ), + files: ["registry/new-york/example/stepper-status.tsx"], }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy(() => import("@/registry/new-york/example/switch-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/switch-demo") + ), files: ["registry/new-york/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch","form"], - component: React.lazy(() => import("@/registry/new-york/example/switch-form")), + registryDependencies: ["switch", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/switch-form") + ), files: ["registry/new-york/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy(() => import("@/registry/new-york/example/table-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/table-demo") + ), files: ["registry/new-york/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy(() => import("@/registry/new-york/example/tabs-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/tabs-demo") + ), files: ["registry/new-york/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/textarea-demo") + ), files: ["registry/new-york/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/textarea-disabled") + ), files: ["registry/new-york/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea","form"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-form")), + registryDependencies: ["textarea", "form"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-form") + ), files: ["registry/new-york/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea","button"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-with-button")), + registryDependencies: ["textarea", "button"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-with-button") + ), files: ["registry/new-york/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-with-label")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-with-label") + ), files: ["registry/new-york/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea","label"], - component: React.lazy(() => import("@/registry/new-york/example/textarea-with-text")), + registryDependencies: ["textarea", "label"], + component: React.lazy( + () => import("@/registry/new-york/example/textarea-with-text") + ), files: ["registry/new-york/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-demo") + ), files: ["registry/new-york/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-destructive")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-destructive") + ), files: ["registry/new-york/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-simple")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-simple") + ), files: ["registry/new-york/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-with-action")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-with-action") + ), files: ["registry/new-york/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy(() => import("@/registry/new-york/example/toast-with-title")), + component: React.lazy( + () => import("@/registry/new-york/example/toast-with-title") + ), files: ["registry/new-york/example/toast-with-title.tsx"], }, "toggle-group-demo": { name: "toggle-group-demo", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-group-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-group-demo") + ), files: ["registry/new-york/example/toggle-group-demo.tsx"], }, "toggle-group-disabled": { name: "toggle-group-disabled", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-group-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-group-disabled") + ), files: ["registry/new-york/example/toggle-group-disabled.tsx"], }, "toggle-group-lg": { name: "toggle-group-lg", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-group-lg")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-group-lg") + ), files: ["registry/new-york/example/toggle-group-lg.tsx"], }, "toggle-group-outline": { name: "toggle-group-outline", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-group-outline")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-group-outline") + ), files: ["registry/new-york/example/toggle-group-outline.tsx"], }, "toggle-group-sm": { name: "toggle-group-sm", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-group-sm")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-group-sm") + ), files: ["registry/new-york/example/toggle-group-sm.tsx"], }, "toggle-group-single": { name: "toggle-group-single", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-group-single")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-group-single") + ), files: ["registry/new-york/example/toggle-group-single.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-demo") + ), files: ["registry/new-york/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-disabled")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-disabled") + ), files: ["registry/new-york/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-lg")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-lg") + ), files: ["registry/new-york/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-outline")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-outline") + ), files: ["registry/new-york/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-sm")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-sm") + ), files: ["registry/new-york/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy(() => import("@/registry/new-york/example/toggle-with-text")), + component: React.lazy( + () => import("@/registry/new-york/example/toggle-with-text") + ), files: ["registry/new-york/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy(() => import("@/registry/new-york/example/tooltip-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/tooltip-demo") + ), files: ["registry/new-york/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-blockquote")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-blockquote") + ), files: ["registry/new-york/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-demo")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-demo") + ), files: ["registry/new-york/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h1")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h1") + ), files: ["registry/new-york/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h2")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h2") + ), files: ["registry/new-york/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h3")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h3") + ), files: ["registry/new-york/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-h4")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-h4") + ), files: ["registry/new-york/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-inline-code")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-inline-code") + ), files: ["registry/new-york/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-large")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-large") + ), files: ["registry/new-york/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-lead")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-lead") + ), files: ["registry/new-york/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-list")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-list") + ), files: ["registry/new-york/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-muted")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-muted") + ), files: ["registry/new-york/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-p")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-p") + ), files: ["registry/new-york/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-small")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-small") + ), files: ["registry/new-york/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/typography-table")), + component: React.lazy( + () => import("@/registry/new-york/example/typography-table") + ), files: ["registry/new-york/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy(() => import("@/registry/new-york/example/mode-toggle")), + component: React.lazy( + () => import("@/registry/new-york/example/mode-toggle") + ), files: ["registry/new-york/example/mode-toggle.tsx"], }, - "cards": { + cards: { name: "cards", type: "components:example", registryDependencies: undefined, diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 8a84ea90c7a..841fc47215d 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -26,12 +26,27 @@ npx shadcn-ui@latest add stepper Use the Stepper component with useStepper hook -```tsx title="page.tsx" {1,9} +```tsx title="page.tsx" "use client" import { useStepper } from "@/components/ui/use-stepper" export default function Page() { + return ( + + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} + +
+ ) +} + +function Child() { const { currentStep, nextStep, prevStep, ...rest } = useStepper() return ( // ... @@ -53,20 +68,31 @@ export default function Page() { -`use-stepper.tsx` - - - Update the import paths to match your project setup. Use the Stepper component with useStepper hook -```tsx title="page.tsx" {1,9} +```tsx title="page.tsx" "use client" import { useStepper } from "@/components/ui/use-stepper" export default function Page() { + return ( + + {steps.map((step, index) => ( + +
+

Step {index + 1} content

+
+
+ ))} + +
+ ) +} + +function Child() { const { currentStep, nextStep, prevStep, ...rest } = useStepper() return ( // ... @@ -85,63 +111,65 @@ export default function Page() { ```tsx import { Stepper, - StepperConfig, StepperItem, + StepperFooter useStepper, } from "@/components/ui/stepper" ``` -```tsx {1-5} -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] +```tsx {1} +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] -export const StepperDemo = () => { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, - setStep, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( - <> - - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
- +
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )} +
) } ``` @@ -151,53 +179,28 @@ export const StepperDemo = () => { ### `` ```tsx -interface StepperProps extends React.HTMLAttributes { - activeStep: number +interface StepperProps { + steps: { + label: string | React.ReactNode + description?: string | React.ReactNode + icon?: React.ReactNode + optional?: boolean + }[] + initialStep: number orientation?: "vertical" | "horizontal" - state?: "loading" | "error" - responsive?: boolean - onClickStep?: (step: number) => void - successIcon?: React.ReactElement - errorIcon?: React.ReactElement labelOrientation?: "vertical" | "horizontal" - children?: React.ReactNode + scrollTracking?: boolean variant?: "default" | "ghost" | "outline" | "secondary" + status?: "default" | "success" | "error" | "loading" + isClickable?: boolean } ``` ### `` ```tsx -interface StepperItemLabelProps { - label: string | React.ReactNode - description?: string | React.ReactNode - optional?: boolean - optionalLabel?: string | React.ReactNode -} - -interface StepperConfig extends StepperItemLabelProps { - icon?: React.ReactElement -} - -interface StepperItemProps - extends React.HTMLAttributes, - VariantProps, - StepperConfig { - isCompletedStep?: boolean -} - -interface StepperItemStatus { - index: number - isCompletedStep?: boolean - isCurrentStep?: boolean -} - -interface StepperItemProps extends StepperItemProps, StepperItemStatus { - additionalClassName?: { - button?: string - label?: string - description?: string - } +interface StepperItemProps { + onStepperItemClick?: () => void } ``` @@ -206,16 +209,7 @@ interface StepperItemProps extends StepperItemProps, StepperItemStatus { `useStepper` returns values and functions that allow to render the stepper in a modular way. ```tsx -function useStepper( - initialStep: number, - steps: { - label: string | React.ReactNode - description?: string | React.ReactNode - optional?: boolean - optionalLabel?: string | React.ReactNode - icon?: React.ReactElement - }[] -): { +function useStepper(): StepperProps & { nextStep: () => void prevStep: () => void resetSteps: () => void @@ -223,7 +217,8 @@ function useStepper( activeStep: number isDisabledStep: boolean isLastStep: boolean - isOptionalStep: boolean | undefined + isOptionalStep: boolean + isFinished: boolean } ``` @@ -232,3 +227,105 @@ function useStepper( ### Default + +### Orientation + +We can pass the `orientation` prop to the Stepper component to set the orientation as "vertical" or "horizontal". + + + +### Description + +We can add a description to the array of steps + + + +### Label orientation + +If you would like the labels to be positioned below the step icons you can do so using the `labelOrientation` prop on the Stepper component. + + + +### Optional steps + +If you want to make a step optional, you can add `optional: true` in the array of steps. + + + +### Status + +Sometimes it is useful to show visual feedback to the user depending on some asynchronous logic. In this case we can use the `status` prop on the Stepper component to show a loading indicator, a success indicator or an error indicator. + + + +### Custom Icons + +If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component. + + + +### Custom onClick item event + +If you need to control the onClick event when a user clicks on a step, you can set the `onStepperItemClick` prop. This event overrides the default setStep, so you must customize it with `useStepper`. + + + +### Responsive + +If you want the stepper to be responsive you can make it responsive by using some logic of your own so that the `orientation` prop changes according to the screen size. + +For example, you can use a hook like `useMediaQuery` that tells you when the screen becomes a certain size in this way: + +```tsx {8,10} +import { useMediaQuery } from "@/hooks/use-media-query" +import { + Stepper, + ... +} from "@/components/ui/stepper" + +export default function Page() { + const isMobile = useMediaQuery("(max-width: 640px)") + return ( + + {/* ... */} + + ) +} +``` + +### Footer inside the step + +When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To do this, we can change the footer structure and place it below the first internal children of `StepperItem`. + +```tsx +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] + +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+ + + +
+ ) + })} +
+
+ ) +} + +function MyStepperFooter() { + ... +} +``` + +### Scroll tracking + +If you would like the stepper to scroll to the active step when it is not in view you can do so using the `scrollTracking` prop on the Stepper component. diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index e4a1e236008..5d137a79f3c 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -1,60 +1,71 @@ import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, - { label: "Step 2" }, + { label: "Step 2", optional: true }, { label: "Step 3" }, -] satisfies StepperConfig[] +] -export default function StepperClickableSteps() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + + alert(`You clicked on step ${index + 1}!`) + } + > +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - setStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- setStep(step)} activeStep={activeStep}> - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx index 96ab9fccd55..45e6562b6de 100644 --- a/apps/www/registry/default/example/stepper-custom-icons.tsx +++ b/apps/www/registry/default/example/stepper-custom-icons.tsx @@ -1,61 +1,68 @@ -import { Calendar, Twitter, User } from "lucide-react" +import { Calendar, Lock, User } from "lucide-react" import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ - { label: "Step 1", icon: }, - { label: "Step 2", icon: }, - { label: "Step 3", icon: }, -] satisfies StepperConfig[] + { label: "Step 1", icon: }, + { label: "Step 2", icon: }, + { label: "Step 3", icon: }, +] -export default function StepperCustomIcons() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx b/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx deleted file mode 100644 index 735507b3195..00000000000 --- a/apps/www/registry/default/example/stepper-custom-success-error-icon.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { CheckCircle2, XCircle } from "lucide-react" - -import { Button } from "@/registry/default/ui/button" -import { - Stepper, - StepperConfig, - StepperItem, -} from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] - -export default function StepperCustomSuccessErrorIcon() { - const { - nextStep, - prevStep, - resetSteps, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } = useStepper({ - initialStep: 0, - steps, - }) - - return ( -
- } - errorIcon={} - > - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
-
- ) -} diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx index 1265da9aa62..38dd75da331 100644 --- a/apps/www/registry/default/example/stepper-demo.tsx +++ b/apps/www/registry/default/example/stepper-demo.tsx @@ -1,59 +1,62 @@ import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/default/example/stepper-description.tsx b/apps/www/registry/default/example/stepper-description.tsx index 4eb24122c76..d6738702eec 100644 --- a/apps/www/registry/default/example/stepper-description.tsx +++ b/apps/www/registry/default/example/stepper-description.tsx @@ -1,59 +1,66 @@ import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ - { label: "Step 1", description: "Frist description" }, - { label: "Step 2", description: "Second description" }, - { label: "Step 3", description: "Third description" }, -] satisfies StepperConfig[] + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, + { label: "Step 3", description: "Description 3" }, +] -export default function StepperDescriptions() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/default/example/stepper-label-orientation.tsx b/apps/www/registry/default/example/stepper-label-orientation.tsx index 979815e9d08..87e9dbfb36d 100644 --- a/apps/www/registry/default/example/stepper-label-orientation.tsx +++ b/apps/www/registry/default/example/stepper-label-orientation.tsx @@ -1,59 +1,66 @@ import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ - { label: "Step 1", description: "Frist description" }, - { label: "Step 2", description: "Second description" }, - { label: "Step 3", description: "Third description" }, -] satisfies StepperConfig[] + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, + { label: "Step 3", description: "Description 3" }, +] -export default function StepperLabelOrientation() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx index fa2a34fe71c..4949a67660f 100644 --- a/apps/www/registry/default/example/stepper-optional-steps.tsx +++ b/apps/www/registry/default/example/stepper-optional-steps.tsx @@ -1,59 +1,66 @@ import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" const steps = [ { label: "Step 1" }, - { label: "Step 2", optional: true, optionalLabel: "Optional" }, + { label: "Step 2", optional: true }, { label: "Step 3" }, -] satisfies StepperConfig[] +] -export default function StepperOptionalSteps() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index d5229b305e5..97a4f426fbd 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -1,59 +1,62 @@ import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] -export default function StepperOrientation() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/default/example/stepper-states.tsx b/apps/www/registry/default/example/stepper-states.tsx deleted file mode 100644 index acf8370eec4..00000000000 --- a/apps/www/registry/default/example/stepper-states.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useState } from "react" - -import { Button } from "@/registry/default/ui/button" -import { Label } from "@/registry/default/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" -import { - Stepper, - StepperConfig, - StepperItem, -} from "@/registry/default/ui/stepper" -import { useStepper } from "@/registry/default/ui/use-stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] - -export default function StepperStates() { - const { - nextStep, - prevStep, - resetSteps, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } = useStepper({ - initialStep: 0, - steps, - }) - - const [value, setValue] = useState<"loading" | "error">("loading") - - return ( -
- setValue(value as "loading" | "error")} - className="mb-4" - > -
- - -
-
- - -
-
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
-
- ) -} diff --git a/apps/www/registry/default/example/stepper-status.tsx b/apps/www/registry/default/example/stepper-status.tsx new file mode 100644 index 00000000000..11baacd66e5 --- /dev/null +++ b/apps/www/registry/default/example/stepper-status.tsx @@ -0,0 +1,95 @@ +import { useState } from "react" + +import { Button } from "@/registry/default/ui/button" +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" +import { + Stepper, + StepperFooter, + StepperItem, + useStepper, +} from "@/registry/default/ui/stepper" + +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] + +export default function StepperStates() { + const [value, setValue] = useState<"loading" | "error">("loading") + + return ( +
+ setValue(value as "loading" | "error")} + className="mb-4" + > +
+ + +
+
+ + +
+
+ +
+ ) +} + +function StepperDemo({ + status = "default", +}: { + status?: "default" | "loading" | "error" +}) { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { + const { + activeStep, + isLastStep, + isOptionalStep, + isDisabledStep, + nextStep, + prevStep, + resetSteps, + steps, + } = useStepper() + + return ( +
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )} +
+ ) +} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 08f58208932..a686b77708c 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -1,49 +1,79 @@ -import * as React from "react" -import { VariantProps, cva } from "class-variance-authority" +import React from "react" +import { cva } from "class-variance-authority" import { Check, Loader2, X } from "lucide-react" import { cn } from "@/lib/utils" import { Button } from "./button" import { Separator } from "./separator" -import { useMediaQuery } from "./use-stepper" -/********** StepperContext **********/ - -interface StepperContextValue extends StepperProps { +interface StepperProps { + steps: { + label: string | React.ReactNode + description?: string | React.ReactNode + icon?: React.ReactNode + optional?: boolean + }[] + initialStep: number + orientation?: "vertical" | "horizontal" + labelOrientation?: "vertical" | "horizontal" + scrollTracking?: boolean + variant?: "default" | "ghost" | "outline" | "secondary" + status?: "default" | "success" | "error" | "loading" isClickable?: boolean - isError?: boolean - isLoading?: boolean - isVertical?: boolean - isLabelVertical?: boolean - stepCount?: number } -const StepperContext = React.createContext({ +interface ContextStepperProps extends StepperProps { + nextStep: () => void + prevStep: () => void + resetSteps: () => void + setStep: (step: number) => void + activeStep: number +} + +const StepperContext = React.createContext({ + steps: [], + initialStep: 0, + nextStep: () => {}, + prevStep: () => {}, + resetSteps: () => {}, + setStep: () => {}, activeStep: 0, }) -export const useStepperContext = () => React.useContext(StepperContext) - -export const StepperProvider: React.FC<{ - value: StepperContextValue +const StepperProvider = ({ + value, + children, +}: { + value: StepperProps children: React.ReactNode -}> = ({ value, children }) => { - const isError = value.state === "error" - const isLoading = value.state === "loading" +}) => { + const [activeStep, setActiveStep] = React.useState(value.initialStep) + const nextStep = () => { + setActiveStep((prev) => prev + 1) + } + + const prevStep = () => { + setActiveStep((prev) => prev - 1) + } + + const resetSteps = () => { + setActiveStep(value.initialStep) + } - const isVertical = value.orientation === "vertical" - const isLabelVertical = - value.orientation !== "vertical" && value.labelOrientation === "vertical" + const setStep = (step: number) => { + setActiveStep(step) + } return ( {children} @@ -51,117 +81,117 @@ export const StepperProvider: React.FC<{ ) } -/********** Stepper **********/ +export function useStepper() { + const context = React.useContext(StepperContext) -export interface StepperProps extends React.HTMLAttributes { - activeStep: number - orientation?: "vertical" | "horizontal" - state?: "loading" | "error" - responsive?: boolean - onClickStep?: (step: number) => void - successIcon?: React.ReactElement - errorIcon?: React.ReactElement - labelOrientation?: "vertical" | "horizontal" - children?: React.ReactNode - variant?: "default" | "ghost" | "outline" | "secondary" + if (context === undefined) { + throw new Error("useStepper must be used within a StepperProvider") + } + + const isDisabledStep = context.activeStep === 0 + const isLastStep = context.activeStep === context.steps.length - 1 + const isOptionalStep = context.steps[context.activeStep]?.optional + const isFinished = context.activeStep === context.steps.length + + return { + ...context, + isDisabledStep, + isLastStep, + isOptionalStep, + isFinished, + } } -export const Stepper = React.forwardRef( +export const Stepper = React.forwardRef< + HTMLDivElement, + StepperProps & React.HTMLAttributes +>( ( { - activeStep = 0, - state, - responsive = true, - orientation: orientationProp = "horizontal", - onClickStep, + initialStep, + steps, + status = "default", + orientation = "horizontal", labelOrientation = "horizontal", + scrollTracking = false, children, - errorIcon, - successIcon, variant = "default", + isClickable = true, className, ...props }, ref ) => { - const childArr = React.Children.toArray(children) - - const stepCount = childArr.length - - const renderHorizontalContent = () => { - if (activeStep <= childArr.length) { - return React.Children.map(childArr[activeStep], (node) => { - if (!React.isValidElement(node)) return - return React.Children.map( - node.props.children, - (childNode) => childNode - ) - }) - } - return null - } - - const isClickable = !!onClickStep + const footer = [] as React.ReactElement[] - const isMobile = useMediaQuery("(max-width: 43em)") + const items = React.Children.toArray(children).map((child, index) => { + if (!React.isValidElement(child)) { + throw new Error("Stepper children must be valid React elements.") + } + if (child.type !== StepperItem && child.type !== StepperFooter) { + throw new Error( + "Stepper children must be either or ." + ) + } + if (child.type === StepperFooter) { + footer.push(child) + return null + } + const stepperItemProps = { + ...child.props, + step: index, + } - const orientation = isMobile && responsive ? "vertical" : orientationProp + return React.cloneElement(child, stepperItemProps) + }) return ( -
+
+ {items} +
+ {orientation === "horizontal" && ( + {children} )} - > - {React.Children.map(children, (child, i) => { - const isCompletedStep = - (React.isValidElement(child) && child.props.isCompletedStep) ?? - i < activeStep - const isLastStep = i === stepCount - 1 - const isCurrentStep = i === activeStep - - const stepProps = { - index: i, - isCompletedStep, - isCurrentStep, - isLastStep, - } - - if (React.isValidElement(child)) { - return React.cloneElement(child, stepProps) - } - - return null - })} + {footer}
- {orientation === "horizontal" && renderHorizontalContent()}
) } ) -Stepper.displayName = "Stepper" +const HorizontalContent = ({ children }: { children?: React.ReactNode }) => { + const { activeStep, isFinished } = useStepper() + + if (isFinished) { + return null + } + + const activeStepperItem = React.Children.toArray(children)[ + activeStep + ] as React.ReactElement + + const content = activeStepperItem?.props?.children -/********** StepperItem **********/ + return content +} const stepperItemVariants = cva("relative flex flex-row gap-2", { variants: { @@ -173,9 +203,6 @@ const stepperItemVariants = cva("relative flex flex-row gap-2", { true: "flex-col", false: "items-center", }, - isClickable: { - true: "cursor-pointer", - }, }, compoundVariants: [ { @@ -186,265 +213,162 @@ const stepperItemVariants = cva("relative flex flex-row gap-2", { ], }) -export interface StepperConfig extends StepperItemLabelProps { - icon?: React.ReactElement -} - -interface StepProps - extends React.HTMLAttributes, - VariantProps, - StepperConfig { - isCompletedStep?: boolean -} - -interface StepperItemStatus { - index: number - isCompletedStep?: boolean - isCurrentStep?: boolean -} - -export interface StepperItemProps extends StepProps, StepperItemStatus { - additionalClassName?: { - button?: string - label?: string - description?: string +const icons = { + success: , + error: , + loading: , + default: null, +} as const + +export const StepperItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & { + onStepperItemClick?: ( + e: React.MouseEvent + ) => void + } + // @ts-ignore - step is a prop that is added from the Stepper through React.Children. +>(({ step, children, className, onStepperItemClick, ...props }, ref) => { + const { + activeStep, + setStep, + steps, + orientation, + labelOrientation, + scrollTracking, + variant, + status, + isClickable, + } = useStepper() + + const isActive = step === activeStep + const isCompleted = step < activeStep + const isDisabled = step > activeStep && !isClickable + + const isLastStep = step === steps.length - 1 + + const isVertical = orientation === "vertical" + const isVerticalLabel = labelOrientation === "vertical" + + const isError = isActive && status === "error" + + let icon = steps[step].icon || step + 1 + if (status !== "default" && isActive) { + icon = icons[status!] + } + if (isCompleted) { + icon = icons.success } -} - -export const StepperItem = React.forwardRef( - (props, ref) => { - const { - children, - description, - icon: CustomIcon, - index, - isCompletedStep, - isCurrentStep, - isLastStep, - label, - optional, - optionalLabel, - className, - additionalClassName, - ...rest - } = props - - const { - isVertical, - isError, - isLoading, - successIcon: CustomSuccessIcon, - errorIcon: CustomErrorIcon, - isLabelVertical, - onClickStep, - isClickable, - variant, - } = useStepperContext() - - const hasVisited = isCurrentStep || isCompletedStep - - const handleClick = (index: number) => { - if (isClickable && onClickStep) { - onClickStep(index) - } - } - - const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon]) - const Success = React.useMemo( - () => CustomSuccessIcon ?? , - [CustomSuccessIcon] - ) + const content = React.Children.toArray(children).filter( + (child) => React.isValidElement(child) && child.type !== StepperFooter + ) - const Error = React.useMemo( - () => CustomErrorIcon ?? , - [CustomErrorIcon] - ) + const footer = React.Children.toArray(children).filter( + (child) => React.isValidElement(child) && child.type === StepperFooter + )[0] as React.ReactElement - const RenderIcon = React.useMemo(() => { - if (isCompletedStep) return Success - if (isCurrentStep) { - if (isError) return Error - if (isLoading) return - } - if (Icon) return Icon - return (index || 0) + 1 - }, [ - isCompletedStep, - Success, - isCurrentStep, - Icon, - index, - isError, - Error, - isLoading, - ]) + const onClickItem = (e: React.MouseEvent) => { + if (isDisabled) { + return + } + if (onStepperItemClick) { + return onStepperItemClick(e) + } + setStep(step) + } - return ( + return ( +
handleClick(index)} - aria-disabled={!hasVisited} + ref={(node) => { + if (scrollTracking) { + node?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + }} > +
-
+
+
+ {!isLastStep && ( + - {RenderIcon} - - -
- - {(isCurrentStep || isCompletedStep) && children} - + )} + {isVertical && isActive && ( +
+ {content} + {footer} +
+ )}
- ) - } -) +
+ ) +}) StepperItem.displayName = "StepperItem" -/********** StepperItemLabel **********/ - -interface StepperItemLabelProps { - label: string | React.ReactNode - description?: string | React.ReactNode - optional?: boolean - optionalLabel?: string | React.ReactNode - labelClassName?: string - descriptionClassName?: string -} - -const StepperItemLabel = ({ - isCurrentStep, - label, - description, - optional, - optionalLabel, - labelClassName, - descriptionClassName, -}: StepperItemLabelProps & { - isCurrentStep?: boolean -}) => { - const { isLabelVertical } = useStepperContext() - - const shouldRender = !!label || !!description - - const renderOptionalLabel = !!optional && !!optionalLabel - - return shouldRender ? ( -
- {!!label && ( -

- {label} - {renderOptionalLabel && ( - - ({optionalLabel}) - - )} -

- )} - {!!description && ( -

- {description} -

- )} +export const StepperFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ children, ...props }, ref) => { + return ( +
+ {children}
- ) : null -} - -StepperItemLabel.displayName = "StepperItemLabel" - -/********** StepperItemConnector **********/ - -interface StepperItemConnectorProps - extends React.HTMLAttributes { - isCompletedStep: boolean - isLastStep?: boolean | null - hasLabel?: boolean - index: number -} - -const StepperItemConnector = React.memo( - ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => { - const { isVertical } = useStepperContext() - - if (isVertical) { - return ( -
- {!isCompletedStep && ( -
{children}
- )} -
- ) - } - - if (isLastStep) { - return null - } - - return ( - - ) - } -) + ) +}) -StepperItemConnector.displayName = "StepperItemConnector" +StepperFooter.displayName = "StepperFooter" diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx index a796595c0b8..7a0429b5b1f 100644 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -1,60 +1,71 @@ import { Button } from "@/registry/new-york/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, - { label: "Step 2" }, + { label: "Step 2", optional: true }, { label: "Step 3" }, -] satisfies StepperConfig[] +] -export default function StepperClickableSteps() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + + alert(`You clicked on step ${index + 1}!`) + } + > +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - setStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- setStep(step)} activeStep={activeStep}> - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx index debb4713a79..c6312b0f1d5 100644 --- a/apps/www/registry/new-york/example/stepper-custom-icons.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-icons.tsx @@ -1,61 +1,68 @@ -import { Calendar, Twitter, User } from "lucide-react" +import { Calendar, Lock, User } from "lucide-react" import { Button } from "@/registry/new-york/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ - { label: "Step 1", icon: }, - { label: "Step 2", icon: }, - { label: "Step 3", icon: }, -] satisfies StepperConfig[] + { label: "Step 1", icon: }, + { label: "Step 2", icon: }, + { label: "Step 3", icon: }, +] -export default function StepperCustomIcons() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx b/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx deleted file mode 100644 index 7069bdef706..00000000000 --- a/apps/www/registry/new-york/example/stepper-custom-success-error-icon.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { CheckCircle2, XCircle } from "lucide-react" - -import { Button } from "@/registry/new-york/ui/button" -import { - Stepper, - StepperConfig, - StepperItem, -} from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] - -export default function StepperCustomSuccessErrorIcon() { - const { - nextStep, - prevStep, - resetSteps, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } = useStepper({ - initialStep: 0, - steps, - }) - - return ( -
- } - errorIcon={} - > - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
-
- ) -} diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index 63f1fccdc48..485a0d7b5b7 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -1,6 +1,3 @@ -import { useEffect, useRef } from "react" -import { Moon } from "lucide-react" - import { Button } from "@/registry/new-york/ui/button" import { Stepper, @@ -9,35 +6,21 @@ import { useStepper, } from "@/registry/new-york/ui/stepper" -const steps = [ - { label: "Step 1", description: "Testing" }, - { label: "Step 2" }, - { label: "Step 3" }, - // { label: "Step 4" }, - // { label: "Step 5" }, - // { label: "Step 6" }, - // { label: "Step 7" }, - // { label: "Step 8" }, - // { label: "Step 9" }, - // { label: "Step 10" }, -] +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] export default function StepperDemo() { return (
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} + + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} diff --git a/apps/www/registry/new-york/example/stepper-description.tsx b/apps/www/registry/new-york/example/stepper-description.tsx index f6020fbd823..07fafa38e3a 100644 --- a/apps/www/registry/new-york/example/stepper-description.tsx +++ b/apps/www/registry/new-york/example/stepper-description.tsx @@ -1,59 +1,66 @@ import { Button } from "@/registry/new-york/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ - { label: "Step 1", description: "Frist description" }, - { label: "Step 2", description: "Second description" }, - { label: "Step 3", description: "Third description" }, -] satisfies StepperConfig[] + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, + { label: "Step 3", description: "Description 3" }, +] -export default function StepperDescriptions() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/new-york/example/stepper-label-orientation.tsx b/apps/www/registry/new-york/example/stepper-label-orientation.tsx index 2b7bd5fff27..47cb31d07f3 100644 --- a/apps/www/registry/new-york/example/stepper-label-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-label-orientation.tsx @@ -1,59 +1,66 @@ import { Button } from "@/registry/new-york/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ - { label: "Step 1", description: "Frist description" }, - { label: "Step 2", description: "Second description" }, - { label: "Step 3", description: "Third description" }, -] satisfies StepperConfig[] + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, + { label: "Step 3", description: "Description 3" }, +] -export default function StepperLabelOrientation() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx index 0249cd14bc2..4b1a4a46318 100644 --- a/apps/www/registry/new-york/example/stepper-optional-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-optional-steps.tsx @@ -1,59 +1,66 @@ import { Button } from "@/registry/new-york/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" const steps = [ { label: "Step 1" }, - { label: "Step 2", optional: true, optionalLabel: "Optional" }, + { label: "Step 2", optional: true }, { label: "Step 3" }, -] satisfies StepperConfig[] +] -export default function StepperOptionalSteps() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index 1339310c480..6d02189f840 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -1,59 +1,62 @@ import { Button } from "@/registry/new-york/ui/button" import { Stepper, - StepperConfig, + StepperFooter, StepperItem, + useStepper, } from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] -export default function StepperOrientation() { +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { const { - nextStep, - prevStep, - resetSteps, activeStep, - isDisabledStep, isLastStep, isOptionalStep, - } = useStepper({ - initialStep: 0, + isDisabledStep, + nextStep, + prevStep, + resetSteps, steps, - }) + } = useStepper() return ( -
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
+
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )}
) } diff --git a/apps/www/registry/new-york/example/stepper-states.tsx b/apps/www/registry/new-york/example/stepper-states.tsx deleted file mode 100644 index b5413faf6d8..00000000000 --- a/apps/www/registry/new-york/example/stepper-states.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useState } from "react" - -import { Button } from "@/registry/new-york/ui/button" -import { Label } from "@/registry/new-york/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" -import { - Stepper, - StepperConfig, - StepperItem, -} from "@/registry/new-york/ui/stepper" -import { useStepper } from "@/registry/new-york/ui/use-stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepperConfig[] - -export default function StepperStates() { - const { - nextStep, - prevStep, - resetSteps, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } = useStepper({ - initialStep: 0, - steps, - }) - - const [value, setValue] = useState<"loading" | "error">("loading") - - return ( -
- setValue(value as "loading" | "error")} - className="mb-4" - > -
- - -
-
- - -
-
- - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} -
-
- {activeStep === steps.length ? ( - <> -

All steps completed!

- - - ) : ( - <> - - - - )} -
-
- ) -} diff --git a/apps/www/registry/new-york/example/stepper-status.tsx b/apps/www/registry/new-york/example/stepper-status.tsx new file mode 100644 index 00000000000..914b7f54b80 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-status.tsx @@ -0,0 +1,95 @@ +import { useState } from "react" + +import { Button } from "@/registry/new-york/ui/button" +import { Label } from "@/registry/new-york/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" +import { + Stepper, + StepperFooter, + StepperItem, + useStepper, +} from "@/registry/new-york/ui/stepper" + +const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] + +export default function StepperStates() { + const [value, setValue] = useState<"loading" | "error">("loading") + + return ( +
+ setValue(value as "loading" | "error")} + className="mb-4" + > +
+ + +
+
+ + +
+
+ +
+ ) +} + +function StepperDemo({ + status = "default", +}: { + status?: "default" | "loading" | "error" +}) { + return ( +
+ + {steps.map((step, index) => { + return ( + +
+

Step {index + 1} content

+
+
+ ) + })} + + + +
+
+ ) +} + +function MyStepperFooter() { + const { + activeStep, + isLastStep, + isOptionalStep, + isDisabledStep, + nextStep, + prevStep, + resetSteps, + steps, + } = useStepper() + + return ( +
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )} +
+ ) +} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 4ef5b87d62c..c3b327a664c 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useRef } from "react" +import React from "react" import { cva } from "class-variance-authority" -import { Check } from "lucide-react" +import { Check, Loader2, X } from "lucide-react" import { cn } from "@/lib/utils" -import { useMediaQuery } from "@/hooks/use-media-query" import { Button } from "./button" import { Separator } from "./separator" @@ -12,15 +11,16 @@ interface StepperProps { steps: { label: string | React.ReactNode description?: string | React.ReactNode + icon?: React.ReactNode optional?: boolean }[] initialStep: number orientation?: "vertical" | "horizontal" - responsive?: boolean labelOrientation?: "vertical" | "horizontal" + scrollTracking?: boolean variant?: "default" | "ghost" | "outline" | "secondary" - status?: "default" | "success" | "error" | "warning" | "loading" - clickable?: boolean + status?: "default" | "success" | "error" | "loading" + isClickable?: boolean } interface ContextStepperProps extends StepperProps { @@ -111,20 +111,17 @@ export const Stepper = React.forwardRef< initialStep, steps, status = "default", - orientation: orientationProp = "horizontal", - responsive = true, + orientation = "horizontal", labelOrientation = "horizontal", + scrollTracking = false, children, variant = "default", - clickable = true, + isClickable = true, className, ...props }, ref ) => { - const isMobile = useMediaQuery("(max-width: 43em)") - const orientation = isMobile && responsive ? "vertical" : orientationProp - const footer = [] as React.ReactElement[] const items = React.Children.toArray(children).map((child, index) => { @@ -154,17 +151,17 @@ export const Stepper = React.forwardRef< steps, initialStep, orientation, - responsive, labelOrientation, + scrollTracking, variant, - clickable, + isClickable, status, }} >
@@ -196,7 +193,7 @@ const HorizontalContent = ({ children }: { children?: React.ReactNode }) => { return content } -const stepperItemVariants = cva("relative flex flex-row gap-4", { +const stepperItemVariants = cva("relative flex flex-row gap-2", { variants: { isLastStep: { true: "flex-[0_0_auto] justify-end", @@ -216,28 +213,37 @@ const stepperItemVariants = cva("relative flex flex-row gap-4", { ], }) +const icons = { + success: , + error: , + loading: , + default: null, +} as const + export const StepperItem = React.forwardRef< HTMLDivElement, - Omit, "onClick"> & { - icon?: React.ReactNode - onClick?: (e: React.MouseEvent) => void + React.HTMLAttributes & { + onStepperItemClick?: ( + e: React.MouseEvent + ) => void } // @ts-ignore - step is a prop that is added from the Stepper through React.Children. ->(({ step, children, className, onClick, icon, ...props }, ref) => { +>(({ step, children, className, onStepperItemClick, ...props }, ref) => { const { activeStep, setStep, steps, orientation, labelOrientation, + scrollTracking, variant, status, - clickable, + isClickable, } = useStepper() const isActive = step === activeStep const isCompleted = step < activeStep - const isDisabled = step > activeStep && !clickable + const isDisabled = step > activeStep && !isClickable const isLastStep = step === steps.length - 1 @@ -246,7 +252,13 @@ export const StepperItem = React.forwardRef< const isError = isActive && status === "error" - const buttonRef = useRef(null) + let icon = steps[step].icon || step + 1 + if (status !== "default" && isActive) { + icon = icons[status!] + } + if (isCompleted) { + icon = icons.success + } const content = React.Children.toArray(children).filter( (child) => React.isValidElement(child) && child.type !== StepperFooter @@ -260,25 +272,16 @@ export const StepperItem = React.forwardRef< if (isDisabled) { return } + if (onStepperItemClick) { + return onStepperItemClick(e) + } setStep(step) - onClick?.(e) } - useEffect(() => { - if (!isActive) { - return - } - buttonRef.current?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - }, [isActive]) - - const iconComponent = icon || step + 1 - return (
{ + if (scrollTracking) { + node?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + }} >
-
+
{!isLastStep && ( )} {isVertical && isActive && ( diff --git a/apps/www/registry/registry.ts b/apps/www/registry/registry.ts index c23787bc7ca..0e41ccfff43 100644 --- a/apps/www/registry/registry.ts +++ b/apps/www/registry/registry.ts @@ -822,12 +822,6 @@ const example: Registry = [ registryDependencies: ["stepper"], files: ["example/stepper-custom-icons.tsx"], }, - { - name: "stepper-custom-success-error-icon", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-custom-success-error-icon.tsx"], - }, { name: "stepper-label-orientation", type: "components:example", @@ -847,10 +841,10 @@ const example: Registry = [ files: ["example/stepper-optional-steps.tsx"], }, { - name: "stepper-states", + name: "stepper-status", type: "components:example", registryDependencies: ["stepper"], - files: ["example/stepper-states.tsx"], + files: ["example/stepper-status.tsx"], }, { name: "switch-demo", From 5f514a29a198287d9f151e3f3d6a073978c901a3 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 14 Jan 2024 02:31:14 -0300 Subject: [PATCH 13/62] chore: update registry --- apps/www/__registry__/index.tsx | 1427 +++++------------ .../registry/styles/default/stepper.json | 2 +- 2 files changed, 423 insertions(+), 1006 deletions(-) diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index a7f57a95bac..cc977405663 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -4,15 +4,15 @@ import * as React from "react" export const Index: Record = { - default: { - accordion: { + "default": { + "accordion": { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/accordion")), files: ["registry/default/ui/accordion.tsx"], }, - alert: { + "alert": { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -33,63 +33,63 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/aspect-ratio")), files: ["registry/default/ui/aspect-ratio.tsx"], }, - avatar: { + "avatar": { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/avatar")), files: ["registry/default/ui/avatar.tsx"], }, - badge: { + "badge": { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/badge")), files: ["registry/default/ui/badge.tsx"], }, - button: { + "button": { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/button")), files: ["registry/default/ui/button.tsx"], }, - calendar: { + "calendar": { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/calendar")), files: ["registry/default/ui/calendar.tsx"], }, - card: { + "card": { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/card")), files: ["registry/default/ui/card.tsx"], }, - carousel: { + "carousel": { name: "carousel", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/carousel")), files: ["registry/default/ui/carousel.tsx"], }, - checkbox: { + "checkbox": { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/checkbox")), files: ["registry/default/ui/checkbox.tsx"], }, - collapsible: { + "collapsible": { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/collapsible")), files: ["registry/default/ui/collapsible.tsx"], }, - command: { + "command": { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -103,14 +103,14 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/context-menu")), files: ["registry/default/ui/context-menu.tsx"], }, - dialog: { + "dialog": { name: "dialog", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/dialog")), files: ["registry/default/ui/dialog.tsx"], }, - drawer: { + "drawer": { name: "drawer", type: "components:ui", registryDependencies: undefined, @@ -121,15 +121,13 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/ui/dropdown-menu") - ), + component: React.lazy(() => import("@/registry/default/ui/dropdown-menu")), files: ["registry/default/ui/dropdown-menu.tsx"], }, - form: { + "form": { name: "form", type: "components:ui", - registryDependencies: ["button", "label"], + registryDependencies: ["button","label"], component: React.lazy(() => import("@/registry/default/ui/form")), files: ["registry/default/ui/form.tsx"], }, @@ -140,21 +138,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/hover-card")), files: ["registry/default/ui/hover-card.tsx"], }, - input: { + "input": { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/input")), files: ["registry/default/ui/input.tsx"], }, - label: { + "label": { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/label")), files: ["registry/default/ui/label.tsx"], }, - menubar: { + "menubar": { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -165,26 +163,24 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/ui/navigation-menu") - ), + component: React.lazy(() => import("@/registry/default/ui/navigation-menu")), files: ["registry/default/ui/navigation-menu.tsx"], }, - pagination: { + "pagination": { name: "pagination", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/default/ui/pagination")), files: ["registry/default/ui/pagination.tsx"], }, - popover: { + "popover": { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/popover")), files: ["registry/default/ui/popover.tsx"], }, - progress: { + "progress": { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -198,7 +194,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/radio-group")), files: ["registry/default/ui/radio-group.tsx"], }, - resizable: { + "resizable": { name: "resizable", type: "components:ui", registryDependencies: undefined, @@ -212,98 +208,91 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/scroll-area")), files: ["registry/default/ui/scroll-area.tsx"], }, - select: { + "select": { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/select")), files: ["registry/default/ui/select.tsx"], }, - separator: { + "separator": { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/separator")), files: ["registry/default/ui/separator.tsx"], }, - sheet: { + "sheet": { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sheet")), files: ["registry/default/ui/sheet.tsx"], }, - skeleton: { + "skeleton": { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/skeleton")), files: ["registry/default/ui/skeleton.tsx"], }, - slider: { + "slider": { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/slider")), files: ["registry/default/ui/slider.tsx"], }, - sonner: { + "sonner": { name: "sonner", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/sonner")), files: ["registry/default/ui/sonner.tsx"], }, - stepper: { + "stepper": { name: "stepper", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/stepper")), - files: [ - "registry/default/ui/stepper.tsx", - "registry/default/ui/use-stepper.ts", - ], + files: ["registry/default/ui/stepper.tsx","registry/default/ui/use-stepper.ts"], }, - switch: { + "switch": { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/switch")), files: ["registry/default/ui/switch.tsx"], }, - table: { + "table": { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/table")), files: ["registry/default/ui/table.tsx"], }, - tabs: { + "tabs": { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/tabs")), files: ["registry/default/ui/tabs.tsx"], }, - textarea: { + "textarea": { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/textarea")), files: ["registry/default/ui/textarea.tsx"], }, - toast: { + "toast": { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/toast")), - files: [ - "registry/default/ui/toast.tsx", - "registry/default/ui/use-toast.ts", - "registry/default/ui/toaster.tsx", - ], + files: ["registry/default/ui/toast.tsx","registry/default/ui/use-toast.ts","registry/default/ui/toaster.tsx"], }, - toggle: { + "toggle": { name: "toggle", type: "components:ui", registryDependencies: undefined, @@ -317,7 +306,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/ui/toggle-group")), files: ["registry/default/ui/toggle-group.tsx"], }, - tooltip: { + "tooltip": { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -328,1261 +317,984 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy( - () => import("@/registry/default/example/accordion-demo") - ), + component: React.lazy(() => import("@/registry/default/example/accordion-demo")), files: ["registry/default/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/default/example/alert-demo") - ), + component: React.lazy(() => import("@/registry/default/example/alert-demo")), files: ["registry/default/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/default/example/alert-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/alert-destructive")), files: ["registry/default/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog", "button"], - component: React.lazy( - () => import("@/registry/default/example/alert-dialog-demo") - ), + registryDependencies: ["alert-dialog","button"], + component: React.lazy(() => import("@/registry/default/example/alert-dialog-demo")), files: ["registry/default/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy( - () => import("@/registry/default/example/aspect-ratio-demo") - ), + component: React.lazy(() => import("@/registry/default/example/aspect-ratio-demo")), files: ["registry/default/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy( - () => import("@/registry/default/example/avatar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/avatar-demo")), files: ["registry/default/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-demo") - ), + component: React.lazy(() => import("@/registry/default/example/badge-demo")), files: ["registry/default/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/badge-destructive")), files: ["registry/default/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-outline") - ), + component: React.lazy(() => import("@/registry/default/example/badge-outline")), files: ["registry/default/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/default/example/badge-secondary") - ), + component: React.lazy(() => import("@/registry/default/example/badge-secondary")), files: ["registry/default/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-demo") - ), + component: React.lazy(() => import("@/registry/default/example/button-demo")), files: ["registry/default/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-secondary") - ), + component: React.lazy(() => import("@/registry/default/example/button-secondary")), files: ["registry/default/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/button-destructive")), files: ["registry/default/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-outline") - ), + component: React.lazy(() => import("@/registry/default/example/button-outline")), files: ["registry/default/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-ghost") - ), + component: React.lazy(() => import("@/registry/default/example/button-ghost")), files: ["registry/default/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-link") - ), + component: React.lazy(() => import("@/registry/default/example/button-link")), files: ["registry/default/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-with-icon") - ), + component: React.lazy(() => import("@/registry/default/example/button-with-icon")), files: ["registry/default/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-loading") - ), + component: React.lazy(() => import("@/registry/default/example/button-loading")), files: ["registry/default/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-icon") - ), + component: React.lazy(() => import("@/registry/default/example/button-icon")), files: ["registry/default/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/default/example/button-as-child") - ), + component: React.lazy(() => import("@/registry/default/example/button-as-child")), files: ["registry/default/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy( - () => import("@/registry/default/example/calendar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/calendar-demo")), files: ["registry/default/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/default/example/calendar-form") - ), + registryDependencies: ["calendar","form","popover"], + component: React.lazy(() => import("@/registry/default/example/calendar-form")), files: ["registry/default/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card", "button", "switch"], - component: React.lazy( - () => import("@/registry/default/example/card-demo") - ), + registryDependencies: ["card","button","switch"], + component: React.lazy(() => import("@/registry/default/example/card-demo")), files: ["registry/default/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button", "card", "input", "label", "select"], - component: React.lazy( - () => import("@/registry/default/example/card-with-form") - ), + registryDependencies: ["button","card","input","label","select"], + component: React.lazy(() => import("@/registry/default/example/card-with-form")), files: ["registry/default/example/card-with-form.tsx"], }, "carousel-demo": { name: "carousel-demo", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-demo") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-demo")), files: ["registry/default/example/carousel-demo.tsx"], }, "carousel-size": { name: "carousel-size", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-size") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-size")), files: ["registry/default/example/carousel-size.tsx"], }, "carousel-spacing": { name: "carousel-spacing", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-spacing") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-spacing")), files: ["registry/default/example/carousel-spacing.tsx"], }, "carousel-orientation": { name: "carousel-orientation", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-orientation") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-orientation")), files: ["registry/default/example/carousel-orientation.tsx"], }, "carousel-api": { name: "carousel-api", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-api") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-api")), files: ["registry/default/example/carousel-api.tsx"], }, "carousel-plugin": { name: "carousel-plugin", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/default/example/carousel-plugin") - ), + component: React.lazy(() => import("@/registry/default/example/carousel-plugin")), files: ["registry/default/example/carousel-plugin.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-demo") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-demo")), files: ["registry/default/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-disabled")), files: ["registry/default/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-form-multiple") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/default/example/checkbox-form-multiple")), files: ["registry/default/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-form-single") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/default/example/checkbox-form-single")), files: ["registry/default/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/default/example/checkbox-with-text") - ), + component: React.lazy(() => import("@/registry/default/example/checkbox-with-text")), files: ["registry/default/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy( - () => import("@/registry/default/example/collapsible-demo") - ), + component: React.lazy(() => import("@/registry/default/example/collapsible-demo")), files: ["registry/default/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/default/example/combobox-demo") - ), + component: React.lazy(() => import("@/registry/default/example/combobox-demo")), files: ["registry/default/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command", "dropdown-menu", "button"], - component: React.lazy( - () => import("@/registry/default/example/combobox-dropdown-menu") - ), + registryDependencies: ["command","dropdown-menu","button"], + component: React.lazy(() => import("@/registry/default/example/combobox-dropdown-menu")), files: ["registry/default/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command", "form"], - component: React.lazy( - () => import("@/registry/default/example/combobox-form") - ), + registryDependencies: ["command","form"], + component: React.lazy(() => import("@/registry/default/example/combobox-form")), files: ["registry/default/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox", "popover"], - component: React.lazy( - () => import("@/registry/default/example/combobox-popover") - ), + registryDependencies: ["combobox","popover"], + component: React.lazy(() => import("@/registry/default/example/combobox-popover")), files: ["registry/default/example/combobox-popover.tsx"], }, "combobox-responsive": { name: "combobox-responsive", type: "components:example", - registryDependencies: ["combobox", "popover", "drawer"], - component: React.lazy( - () => import("@/registry/default/example/combobox-responsive") - ), + registryDependencies: ["combobox","popover","drawer"], + component: React.lazy(() => import("@/registry/default/example/combobox-responsive")), files: ["registry/default/example/combobox-responsive.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/default/example/command-demo") - ), + component: React.lazy(() => import("@/registry/default/example/command-demo")), files: ["registry/default/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command", "dialog"], - component: React.lazy( - () => import("@/registry/default/example/command-dialog") - ), + registryDependencies: ["command","dialog"], + component: React.lazy(() => import("@/registry/default/example/command-dialog")), files: ["registry/default/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy( - () => import("@/registry/default/example/context-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/context-menu-demo")), files: ["registry/default/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy( - () => import("@/registry/default/example/data-table-demo") - ), + component: React.lazy(() => import("@/registry/default/example/data-table-demo")), files: ["registry/default/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-demo") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-demo")), files: ["registry/default/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button", "calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-form") - ), + registryDependencies: ["button","calendar","form","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-form")), files: ["registry/default/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button", "calendar", "popover", "select"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-with-presets") - ), + registryDependencies: ["button","calendar","popover","select"], + component: React.lazy(() => import("@/registry/default/example/date-picker-with-presets")), files: ["registry/default/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/default/example/date-picker-with-range") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/default/example/date-picker-with-range")), files: ["registry/default/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy( - () => import("@/registry/default/example/dialog-demo") - ), + component: React.lazy(() => import("@/registry/default/example/dialog-demo")), files: ["registry/default/example/dialog-demo.tsx"], }, "dialog-close-button": { name: "dialog-close-button", type: "components:example", - registryDependencies: ["dialog", "button"], - component: React.lazy( - () => import("@/registry/default/example/dialog-close-button") - ), + registryDependencies: ["dialog","button"], + component: React.lazy(() => import("@/registry/default/example/dialog-close-button")), files: ["registry/default/example/dialog-close-button.tsx"], }, "drawer-demo": { name: "drawer-demo", type: "components:example", registryDependencies: ["drawer"], - component: React.lazy( - () => import("@/registry/default/example/drawer-demo") - ), + component: React.lazy(() => import("@/registry/default/example/drawer-demo")), files: ["registry/default/example/drawer-demo.tsx"], }, "drawer-dialog": { name: "drawer-dialog", type: "components:example", - registryDependencies: ["drawer", "dialog"], - component: React.lazy( - () => import("@/registry/default/example/drawer-dialog") - ), + registryDependencies: ["drawer","dialog"], + component: React.lazy(() => import("@/registry/default/example/drawer-dialog")), files: ["registry/default/example/drawer-dialog.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-demo")), files: ["registry/default/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu", "checkbox"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-checkboxes") - ), + registryDependencies: ["dropdown-menu","checkbox"], + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-checkboxes")), files: ["registry/default/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu", "radio-group"], - component: React.lazy( - () => import("@/registry/default/example/dropdown-menu-radio-group") - ), + registryDependencies: ["dropdown-menu","radio-group"], + component: React.lazy(() => import("@/registry/default/example/dropdown-menu-radio-group")), files: ["registry/default/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy( - () => import("@/registry/default/example/hover-card-demo") - ), + component: React.lazy(() => import("@/registry/default/example/hover-card-demo")), files: ["registry/default/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-demo") - ), + component: React.lazy(() => import("@/registry/default/example/input-demo")), files: ["registry/default/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/input-disabled")), files: ["registry/default/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/default/example/input-file") - ), + component: React.lazy(() => import("@/registry/default/example/input-file")), files: ["registry/default/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input", "button", "form"], - component: React.lazy( - () => import("@/registry/default/example/input-form") - ), + registryDependencies: ["input","button","form"], + component: React.lazy(() => import("@/registry/default/example/input-form")), files: ["registry/default/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input", "button"], - component: React.lazy( - () => import("@/registry/default/example/input-with-button") - ), + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/default/example/input-with-button")), files: ["registry/default/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/default/example/input-with-label") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/default/example/input-with-label")), files: ["registry/default/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/default/example/input-with-text") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/default/example/input-with-text")), files: ["registry/default/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy( - () => import("@/registry/default/example/label-demo") - ), + component: React.lazy(() => import("@/registry/default/example/label-demo")), files: ["registry/default/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy( - () => import("@/registry/default/example/menubar-demo") - ), + component: React.lazy(() => import("@/registry/default/example/menubar-demo")), files: ["registry/default/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy( - () => import("@/registry/default/example/navigation-menu-demo") - ), + component: React.lazy(() => import("@/registry/default/example/navigation-menu-demo")), files: ["registry/default/example/navigation-menu-demo.tsx"], }, "pagination-demo": { name: "pagination-demo", type: "components:example", registryDependencies: ["pagination"], - component: React.lazy( - () => import("@/registry/default/example/pagination-demo") - ), + component: React.lazy(() => import("@/registry/default/example/pagination-demo")), files: ["registry/default/example/pagination-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy( - () => import("@/registry/default/example/popover-demo") - ), + component: React.lazy(() => import("@/registry/default/example/popover-demo")), files: ["registry/default/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy( - () => import("@/registry/default/example/progress-demo") - ), + component: React.lazy(() => import("@/registry/default/example/progress-demo")), files: ["registry/default/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy( - () => import("@/registry/default/example/radio-group-demo") - ), + component: React.lazy(() => import("@/registry/default/example/radio-group-demo")), files: ["registry/default/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group", "form"], - component: React.lazy( - () => import("@/registry/default/example/radio-group-form") - ), + registryDependencies: ["radio-group","form"], + component: React.lazy(() => import("@/registry/default/example/radio-group-form")), files: ["registry/default/example/radio-group-form.tsx"], }, "resizable-demo": { name: "resizable-demo", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-demo") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-demo")), files: ["registry/default/example/resizable-demo.tsx"], }, "resizable-demo-with-handle": { name: "resizable-demo-with-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-demo-with-handle") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-demo-with-handle")), files: ["registry/default/example/resizable-demo-with-handle.tsx"], }, "resizable-vertical": { name: "resizable-vertical", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-vertical") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-vertical")), files: ["registry/default/example/resizable-vertical.tsx"], }, "resizable-handle": { name: "resizable-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/default/example/resizable-handle") - ), + component: React.lazy(() => import("@/registry/default/example/resizable-handle")), files: ["registry/default/example/resizable-handle.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/default/example/scroll-area-demo") - ), + component: React.lazy(() => import("@/registry/default/example/scroll-area-demo")), files: ["registry/default/example/scroll-area-demo.tsx"], }, "scroll-area-horizontal-demo": { name: "scroll-area-horizontal-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/default/example/scroll-area-horizontal-demo") - ), + component: React.lazy(() => import("@/registry/default/example/scroll-area-horizontal-demo")), files: ["registry/default/example/scroll-area-horizontal-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-demo") - ), + component: React.lazy(() => import("@/registry/default/example/select-demo")), files: ["registry/default/example/select-demo.tsx"], }, "select-scrollable": { name: "select-scrollable", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-scrollable") - ), + component: React.lazy(() => import("@/registry/default/example/select-scrollable")), files: ["registry/default/example/select-scrollable.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/default/example/select-form") - ), + component: React.lazy(() => import("@/registry/default/example/select-form")), files: ["registry/default/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy( - () => import("@/registry/default/example/separator-demo") - ), + component: React.lazy(() => import("@/registry/default/example/separator-demo")), files: ["registry/default/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/default/example/sheet-demo") - ), + component: React.lazy(() => import("@/registry/default/example/sheet-demo")), files: ["registry/default/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/default/example/sheet-side") - ), + component: React.lazy(() => import("@/registry/default/example/sheet-side")), files: ["registry/default/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy( - () => import("@/registry/default/example/skeleton-demo") - ), + component: React.lazy(() => import("@/registry/default/example/skeleton-demo")), files: ["registry/default/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy( - () => import("@/registry/default/example/slider-demo") - ), + component: React.lazy(() => import("@/registry/default/example/slider-demo")), files: ["registry/default/example/slider-demo.tsx"], }, "sonner-demo": { name: "sonner-demo", type: "components:example", registryDependencies: ["sonner"], - component: React.lazy( - () => import("@/registry/default/example/sonner-demo") - ), + component: React.lazy(() => import("@/registry/default/example/sonner-demo")), files: ["registry/default/example/sonner-demo.tsx"], }, "stepper-demo": { name: "stepper-demo", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-demo") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-demo")), files: ["registry/default/example/stepper-demo.tsx"], }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-orientation") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-orientation")), files: ["registry/default/example/stepper-orientation.tsx"], }, "stepper-description": { name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-description") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-description")), files: ["registry/default/example/stepper-description.tsx"], }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-custom-icons") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-custom-icons")), files: ["registry/default/example/stepper-custom-icons.tsx"], }, "stepper-label-orientation": { name: "stepper-label-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-label-orientation") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-label-orientation")), files: ["registry/default/example/stepper-label-orientation.tsx"], }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-clickable-steps") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-clickable-steps")), files: ["registry/default/example/stepper-clickable-steps.tsx"], }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-optional-steps") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-optional-steps")), files: ["registry/default/example/stepper-optional-steps.tsx"], }, "stepper-status": { name: "stepper-status", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/default/example/stepper-status") - ), + component: React.lazy(() => import("@/registry/default/example/stepper-status")), files: ["registry/default/example/stepper-status.tsx"], }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy( - () => import("@/registry/default/example/switch-demo") - ), + component: React.lazy(() => import("@/registry/default/example/switch-demo")), files: ["registry/default/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch", "form"], - component: React.lazy( - () => import("@/registry/default/example/switch-form") - ), + registryDependencies: ["switch","form"], + component: React.lazy(() => import("@/registry/default/example/switch-form")), files: ["registry/default/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy( - () => import("@/registry/default/example/table-demo") - ), + component: React.lazy(() => import("@/registry/default/example/table-demo")), files: ["registry/default/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy( - () => import("@/registry/default/example/tabs-demo") - ), + component: React.lazy(() => import("@/registry/default/example/tabs-demo")), files: ["registry/default/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/default/example/textarea-demo") - ), + component: React.lazy(() => import("@/registry/default/example/textarea-demo")), files: ["registry/default/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/default/example/textarea-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/textarea-disabled")), files: ["registry/default/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea", "form"], - component: React.lazy( - () => import("@/registry/default/example/textarea-form") - ), + registryDependencies: ["textarea","form"], + component: React.lazy(() => import("@/registry/default/example/textarea-form")), files: ["registry/default/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea", "button"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-button") - ), + registryDependencies: ["textarea","button"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-button")), files: ["registry/default/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-label") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-label")), files: ["registry/default/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/default/example/textarea-with-text") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/default/example/textarea-with-text")), files: ["registry/default/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toast-demo")), files: ["registry/default/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-destructive") - ), + component: React.lazy(() => import("@/registry/default/example/toast-destructive")), files: ["registry/default/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-simple") - ), + component: React.lazy(() => import("@/registry/default/example/toast-simple")), files: ["registry/default/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-with-action") - ), + component: React.lazy(() => import("@/registry/default/example/toast-with-action")), files: ["registry/default/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/default/example/toast-with-title") - ), + component: React.lazy(() => import("@/registry/default/example/toast-with-title")), files: ["registry/default/example/toast-with-title.tsx"], }, "toggle-group-demo": { name: "toggle-group-demo", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-demo")), files: ["registry/default/example/toggle-group-demo.tsx"], }, "toggle-group-disabled": { name: "toggle-group-disabled", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-disabled")), files: ["registry/default/example/toggle-group-disabled.tsx"], }, "toggle-group-lg": { name: "toggle-group-lg", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-lg") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-lg")), files: ["registry/default/example/toggle-group-lg.tsx"], }, "toggle-group-outline": { name: "toggle-group-outline", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-outline") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-outline")), files: ["registry/default/example/toggle-group-outline.tsx"], }, "toggle-group-sm": { name: "toggle-group-sm", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-sm") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-sm")), files: ["registry/default/example/toggle-group-sm.tsx"], }, "toggle-group-single": { name: "toggle-group-single", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/default/example/toggle-group-single") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-group-single")), files: ["registry/default/example/toggle-group-single.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-demo") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-demo")), files: ["registry/default/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-disabled") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-disabled")), files: ["registry/default/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-lg") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-lg")), files: ["registry/default/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-outline") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-outline")), files: ["registry/default/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-sm") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-sm")), files: ["registry/default/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/default/example/toggle-with-text") - ), + component: React.lazy(() => import("@/registry/default/example/toggle-with-text")), files: ["registry/default/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy( - () => import("@/registry/default/example/tooltip-demo") - ), + component: React.lazy(() => import("@/registry/default/example/tooltip-demo")), files: ["registry/default/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-blockquote") - ), + component: React.lazy(() => import("@/registry/default/example/typography-blockquote")), files: ["registry/default/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-demo") - ), + component: React.lazy(() => import("@/registry/default/example/typography-demo")), files: ["registry/default/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h1") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h1")), files: ["registry/default/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h2") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h2")), files: ["registry/default/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h3") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h3")), files: ["registry/default/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-h4") - ), + component: React.lazy(() => import("@/registry/default/example/typography-h4")), files: ["registry/default/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-inline-code") - ), + component: React.lazy(() => import("@/registry/default/example/typography-inline-code")), files: ["registry/default/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-large") - ), + component: React.lazy(() => import("@/registry/default/example/typography-large")), files: ["registry/default/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-lead") - ), + component: React.lazy(() => import("@/registry/default/example/typography-lead")), files: ["registry/default/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-list") - ), + component: React.lazy(() => import("@/registry/default/example/typography-list")), files: ["registry/default/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-muted") - ), + component: React.lazy(() => import("@/registry/default/example/typography-muted")), files: ["registry/default/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-p") - ), + component: React.lazy(() => import("@/registry/default/example/typography-p")), files: ["registry/default/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-small") - ), + component: React.lazy(() => import("@/registry/default/example/typography-small")), files: ["registry/default/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/typography-table") - ), + component: React.lazy(() => import("@/registry/default/example/typography-table")), files: ["registry/default/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/default/example/mode-toggle") - ), + component: React.lazy(() => import("@/registry/default/example/mode-toggle")), files: ["registry/default/example/mode-toggle.tsx"], }, - cards: { + "cards": { name: "cards", type: "components:example", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/example/cards")), files: ["registry/default/example/cards/cards.tsx"], }, - }, - "new-york": { - accordion: { + }, "new-york": { + "accordion": { name: "accordion", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/accordion")), files: ["registry/new-york/ui/accordion.tsx"], }, - alert: { + "alert": { name: "alert", type: "components:ui", registryDependencies: undefined, @@ -1593,77 +1305,73 @@ export const Index: Record = { name: "alert-dialog", type: "components:ui", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/ui/alert-dialog") - ), + component: React.lazy(() => import("@/registry/new-york/ui/alert-dialog")), files: ["registry/new-york/ui/alert-dialog.tsx"], }, "aspect-ratio": { name: "aspect-ratio", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/aspect-ratio") - ), + component: React.lazy(() => import("@/registry/new-york/ui/aspect-ratio")), files: ["registry/new-york/ui/aspect-ratio.tsx"], }, - avatar: { + "avatar": { name: "avatar", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/avatar")), files: ["registry/new-york/ui/avatar.tsx"], }, - badge: { + "badge": { name: "badge", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/badge")), files: ["registry/new-york/ui/badge.tsx"], }, - button: { + "button": { name: "button", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/button")), files: ["registry/new-york/ui/button.tsx"], }, - calendar: { + "calendar": { name: "calendar", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/calendar")), files: ["registry/new-york/ui/calendar.tsx"], }, - card: { + "card": { name: "card", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/card")), files: ["registry/new-york/ui/card.tsx"], }, - carousel: { + "carousel": { name: "carousel", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/carousel")), files: ["registry/new-york/ui/carousel.tsx"], }, - checkbox: { + "checkbox": { name: "checkbox", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/checkbox")), files: ["registry/new-york/ui/checkbox.tsx"], }, - collapsible: { + "collapsible": { name: "collapsible", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/collapsible")), files: ["registry/new-york/ui/collapsible.tsx"], }, - command: { + "command": { name: "command", type: "components:ui", registryDependencies: ["dialog"], @@ -1674,19 +1382,17 @@ export const Index: Record = { name: "context-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/context-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/context-menu")), files: ["registry/new-york/ui/context-menu.tsx"], }, - dialog: { + "dialog": { name: "dialog", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/dialog")), files: ["registry/new-york/ui/dialog.tsx"], }, - drawer: { + "drawer": { name: "drawer", type: "components:ui", registryDependencies: undefined, @@ -1697,15 +1403,13 @@ export const Index: Record = { name: "dropdown-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/dropdown-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/dropdown-menu")), files: ["registry/new-york/ui/dropdown-menu.tsx"], }, - form: { + "form": { name: "form", type: "components:ui", - registryDependencies: ["button", "label"], + registryDependencies: ["button","label"], component: React.lazy(() => import("@/registry/new-york/ui/form")), files: ["registry/new-york/ui/form.tsx"], }, @@ -1716,21 +1420,21 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/hover-card")), files: ["registry/new-york/ui/hover-card.tsx"], }, - input: { + "input": { name: "input", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/input")), files: ["registry/new-york/ui/input.tsx"], }, - label: { + "label": { name: "label", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/label")), files: ["registry/new-york/ui/label.tsx"], }, - menubar: { + "menubar": { name: "menubar", type: "components:ui", registryDependencies: undefined, @@ -1741,26 +1445,24 @@ export const Index: Record = { name: "navigation-menu", type: "components:ui", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/ui/navigation-menu") - ), + component: React.lazy(() => import("@/registry/new-york/ui/navigation-menu")), files: ["registry/new-york/ui/navigation-menu.tsx"], }, - pagination: { + "pagination": { name: "pagination", type: "components:ui", registryDependencies: ["button"], component: React.lazy(() => import("@/registry/new-york/ui/pagination")), files: ["registry/new-york/ui/pagination.tsx"], }, - popover: { + "popover": { name: "popover", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/popover")), files: ["registry/new-york/ui/popover.tsx"], }, - progress: { + "progress": { name: "progress", type: "components:ui", registryDependencies: undefined, @@ -1774,7 +1476,7 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/radio-group")), files: ["registry/new-york/ui/radio-group.tsx"], }, - resizable: { + "resizable": { name: "resizable", type: "components:ui", registryDependencies: undefined, @@ -1788,98 +1490,91 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/ui/scroll-area")), files: ["registry/new-york/ui/scroll-area.tsx"], }, - select: { + "select": { name: "select", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/select")), files: ["registry/new-york/ui/select.tsx"], }, - separator: { + "separator": { name: "separator", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/separator")), files: ["registry/new-york/ui/separator.tsx"], }, - sheet: { + "sheet": { name: "sheet", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sheet")), files: ["registry/new-york/ui/sheet.tsx"], }, - skeleton: { + "skeleton": { name: "skeleton", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/skeleton")), files: ["registry/new-york/ui/skeleton.tsx"], }, - slider: { + "slider": { name: "slider", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/slider")), files: ["registry/new-york/ui/slider.tsx"], }, - sonner: { + "sonner": { name: "sonner", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/sonner")), files: ["registry/new-york/ui/sonner.tsx"], }, - stepper: { + "stepper": { name: "stepper", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/stepper")), - files: [ - "registry/new-york/ui/stepper.tsx", - "registry/new-york/ui/use-stepper.ts", - ], + files: ["registry/new-york/ui/stepper.tsx","registry/new-york/ui/use-stepper.ts"], }, - switch: { + "switch": { name: "switch", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/switch")), files: ["registry/new-york/ui/switch.tsx"], }, - table: { + "table": { name: "table", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/table")), files: ["registry/new-york/ui/table.tsx"], }, - tabs: { + "tabs": { name: "tabs", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/tabs")), files: ["registry/new-york/ui/tabs.tsx"], }, - textarea: { + "textarea": { name: "textarea", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/textarea")), files: ["registry/new-york/ui/textarea.tsx"], }, - toast: { + "toast": { name: "toast", type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/toast")), - files: [ - "registry/new-york/ui/toast.tsx", - "registry/new-york/ui/use-toast.ts", - "registry/new-york/ui/toaster.tsx", - ], + files: ["registry/new-york/ui/toast.tsx","registry/new-york/ui/use-toast.ts","registry/new-york/ui/toaster.tsx"], }, - toggle: { + "toggle": { name: "toggle", type: "components:ui", registryDependencies: undefined, @@ -1890,12 +1585,10 @@ export const Index: Record = { name: "toggle-group", type: "components:ui", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/ui/toggle-group") - ), + component: React.lazy(() => import("@/registry/new-york/ui/toggle-group")), files: ["registry/new-york/ui/toggle-group.tsx"], }, - tooltip: { + "tooltip": { name: "tooltip", type: "components:ui", registryDependencies: undefined, @@ -1906,1245 +1599,969 @@ export const Index: Record = { name: "accordion-demo", type: "components:example", registryDependencies: ["accordion"], - component: React.lazy( - () => import("@/registry/new-york/example/accordion-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/accordion-demo")), files: ["registry/new-york/example/accordion-demo.tsx"], }, "alert-demo": { name: "alert-demo", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/alert-demo")), files: ["registry/new-york/example/alert-demo.tsx"], }, "alert-destructive": { name: "alert-destructive", type: "components:example", registryDependencies: ["alert"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/alert-destructive")), files: ["registry/new-york/example/alert-destructive.tsx"], }, "alert-dialog-demo": { name: "alert-dialog-demo", type: "components:example", - registryDependencies: ["alert-dialog", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/alert-dialog-demo") - ), + registryDependencies: ["alert-dialog","button"], + component: React.lazy(() => import("@/registry/new-york/example/alert-dialog-demo")), files: ["registry/new-york/example/alert-dialog-demo.tsx"], }, "aspect-ratio-demo": { name: "aspect-ratio-demo", type: "components:example", registryDependencies: ["aspect-ratio"], - component: React.lazy( - () => import("@/registry/new-york/example/aspect-ratio-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/aspect-ratio-demo")), files: ["registry/new-york/example/aspect-ratio-demo.tsx"], }, "avatar-demo": { name: "avatar-demo", type: "components:example", registryDependencies: ["avatar"], - component: React.lazy( - () => import("@/registry/new-york/example/avatar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/avatar-demo")), files: ["registry/new-york/example/avatar-demo.tsx"], }, "badge-demo": { name: "badge-demo", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-demo")), files: ["registry/new-york/example/badge-demo.tsx"], }, "badge-destructive": { name: "badge-destructive", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-destructive")), files: ["registry/new-york/example/badge-destructive.tsx"], }, "badge-outline": { name: "badge-outline", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-outline")), files: ["registry/new-york/example/badge-outline.tsx"], }, "badge-secondary": { name: "badge-secondary", type: "components:example", registryDependencies: ["badge"], - component: React.lazy( - () => import("@/registry/new-york/example/badge-secondary") - ), + component: React.lazy(() => import("@/registry/new-york/example/badge-secondary")), files: ["registry/new-york/example/badge-secondary.tsx"], }, "button-demo": { name: "button-demo", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-demo")), files: ["registry/new-york/example/button-demo.tsx"], }, "button-secondary": { name: "button-secondary", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-secondary") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-secondary")), files: ["registry/new-york/example/button-secondary.tsx"], }, "button-destructive": { name: "button-destructive", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-destructive")), files: ["registry/new-york/example/button-destructive.tsx"], }, "button-outline": { name: "button-outline", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-outline")), files: ["registry/new-york/example/button-outline.tsx"], }, "button-ghost": { name: "button-ghost", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-ghost") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-ghost")), files: ["registry/new-york/example/button-ghost.tsx"], }, "button-link": { name: "button-link", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-link") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-link")), files: ["registry/new-york/example/button-link.tsx"], }, "button-with-icon": { name: "button-with-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-with-icon") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-with-icon")), files: ["registry/new-york/example/button-with-icon.tsx"], }, "button-loading": { name: "button-loading", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-loading") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-loading")), files: ["registry/new-york/example/button-loading.tsx"], }, "button-icon": { name: "button-icon", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-icon") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-icon")), files: ["registry/new-york/example/button-icon.tsx"], }, "button-as-child": { name: "button-as-child", type: "components:example", registryDependencies: ["button"], - component: React.lazy( - () => import("@/registry/new-york/example/button-as-child") - ), + component: React.lazy(() => import("@/registry/new-york/example/button-as-child")), files: ["registry/new-york/example/button-as-child.tsx"], }, "calendar-demo": { name: "calendar-demo", type: "components:example", registryDependencies: ["calendar"], - component: React.lazy( - () => import("@/registry/new-york/example/calendar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/calendar-demo")), files: ["registry/new-york/example/calendar-demo.tsx"], }, "calendar-form": { name: "calendar-form", type: "components:example", - registryDependencies: ["calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/calendar-form") - ), + registryDependencies: ["calendar","form","popover"], + component: React.lazy(() => import("@/registry/new-york/example/calendar-form")), files: ["registry/new-york/example/calendar-form.tsx"], }, "card-demo": { name: "card-demo", type: "components:example", - registryDependencies: ["card", "button", "switch"], - component: React.lazy( - () => import("@/registry/new-york/example/card-demo") - ), + registryDependencies: ["card","button","switch"], + component: React.lazy(() => import("@/registry/new-york/example/card-demo")), files: ["registry/new-york/example/card-demo.tsx"], }, "card-with-form": { name: "card-with-form", type: "components:example", - registryDependencies: ["button", "card", "input", "label", "select"], - component: React.lazy( - () => import("@/registry/new-york/example/card-with-form") - ), + registryDependencies: ["button","card","input","label","select"], + component: React.lazy(() => import("@/registry/new-york/example/card-with-form")), files: ["registry/new-york/example/card-with-form.tsx"], }, "carousel-demo": { name: "carousel-demo", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-demo")), files: ["registry/new-york/example/carousel-demo.tsx"], }, "carousel-size": { name: "carousel-size", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-size") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-size")), files: ["registry/new-york/example/carousel-size.tsx"], }, "carousel-spacing": { name: "carousel-spacing", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-spacing") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-spacing")), files: ["registry/new-york/example/carousel-spacing.tsx"], }, "carousel-orientation": { name: "carousel-orientation", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-orientation") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-orientation")), files: ["registry/new-york/example/carousel-orientation.tsx"], }, "carousel-api": { name: "carousel-api", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-api") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-api")), files: ["registry/new-york/example/carousel-api.tsx"], }, "carousel-plugin": { name: "carousel-plugin", type: "components:example", registryDependencies: ["carousel"], - component: React.lazy( - () => import("@/registry/new-york/example/carousel-plugin") - ), + component: React.lazy(() => import("@/registry/new-york/example/carousel-plugin")), files: ["registry/new-york/example/carousel-plugin.tsx"], }, "checkbox-demo": { name: "checkbox-demo", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-demo")), files: ["registry/new-york/example/checkbox-demo.tsx"], }, "checkbox-disabled": { name: "checkbox-disabled", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-disabled")), files: ["registry/new-york/example/checkbox-disabled.tsx"], }, "checkbox-form-multiple": { name: "checkbox-form-multiple", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-form-multiple") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-multiple")), files: ["registry/new-york/example/checkbox-form-multiple.tsx"], }, "checkbox-form-single": { name: "checkbox-form-single", type: "components:example", - registryDependencies: ["checkbox", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-form-single") - ), + registryDependencies: ["checkbox","form"], + component: React.lazy(() => import("@/registry/new-york/example/checkbox-form-single")), files: ["registry/new-york/example/checkbox-form-single.tsx"], }, "checkbox-with-text": { name: "checkbox-with-text", type: "components:example", registryDependencies: ["checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/checkbox-with-text") - ), + component: React.lazy(() => import("@/registry/new-york/example/checkbox-with-text")), files: ["registry/new-york/example/checkbox-with-text.tsx"], }, "collapsible-demo": { name: "collapsible-demo", type: "components:example", registryDependencies: ["collapsible"], - component: React.lazy( - () => import("@/registry/new-york/example/collapsible-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/collapsible-demo")), files: ["registry/new-york/example/collapsible-demo.tsx"], }, "combobox-demo": { name: "combobox-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/combobox-demo")), files: ["registry/new-york/example/combobox-demo.tsx"], }, "combobox-dropdown-menu": { name: "combobox-dropdown-menu", type: "components:example", - registryDependencies: ["command", "dropdown-menu", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-dropdown-menu") - ), + registryDependencies: ["command","dropdown-menu","button"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-dropdown-menu")), files: ["registry/new-york/example/combobox-dropdown-menu.tsx"], }, "combobox-form": { name: "combobox-form", type: "components:example", - registryDependencies: ["command", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-form") - ), + registryDependencies: ["command","form"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-form")), files: ["registry/new-york/example/combobox-form.tsx"], }, "combobox-popover": { name: "combobox-popover", type: "components:example", - registryDependencies: ["combobox", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-popover") - ), + registryDependencies: ["combobox","popover"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-popover")), files: ["registry/new-york/example/combobox-popover.tsx"], }, "combobox-responsive": { name: "combobox-responsive", type: "components:example", - registryDependencies: ["combobox", "popover", "drawer"], - component: React.lazy( - () => import("@/registry/new-york/example/combobox-responsive") - ), + registryDependencies: ["combobox","popover","drawer"], + component: React.lazy(() => import("@/registry/new-york/example/combobox-responsive")), files: ["registry/new-york/example/combobox-responsive.tsx"], }, "command-demo": { name: "command-demo", type: "components:example", registryDependencies: ["command"], - component: React.lazy( - () => import("@/registry/new-york/example/command-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/command-demo")), files: ["registry/new-york/example/command-demo.tsx"], }, "command-dialog": { name: "command-dialog", type: "components:example", - registryDependencies: ["command", "dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/command-dialog") - ), + registryDependencies: ["command","dialog"], + component: React.lazy(() => import("@/registry/new-york/example/command-dialog")), files: ["registry/new-york/example/command-dialog.tsx"], }, "context-menu-demo": { name: "context-menu-demo", type: "components:example", registryDependencies: ["context-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/context-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/context-menu-demo")), files: ["registry/new-york/example/context-menu-demo.tsx"], }, "data-table-demo": { name: "data-table-demo", type: "components:example", registryDependencies: ["data-table"], - component: React.lazy( - () => import("@/registry/new-york/example/data-table-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/data-table-demo")), files: ["registry/new-york/example/data-table-demo.tsx"], }, "date-picker-demo": { name: "date-picker-demo", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-demo") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-demo")), files: ["registry/new-york/example/date-picker-demo.tsx"], }, "date-picker-form": { name: "date-picker-form", type: "components:example", - registryDependencies: ["button", "calendar", "form", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-form") - ), + registryDependencies: ["button","calendar","form","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-form")), files: ["registry/new-york/example/date-picker-form.tsx"], }, "date-picker-with-presets": { name: "date-picker-with-presets", type: "components:example", - registryDependencies: ["button", "calendar", "popover", "select"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-with-presets") - ), + registryDependencies: ["button","calendar","popover","select"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-presets")), files: ["registry/new-york/example/date-picker-with-presets.tsx"], }, "date-picker-with-range": { name: "date-picker-with-range", type: "components:example", - registryDependencies: ["button", "calendar", "popover"], - component: React.lazy( - () => import("@/registry/new-york/example/date-picker-with-range") - ), + registryDependencies: ["button","calendar","popover"], + component: React.lazy(() => import("@/registry/new-york/example/date-picker-with-range")), files: ["registry/new-york/example/date-picker-with-range.tsx"], }, "dialog-demo": { name: "dialog-demo", type: "components:example", registryDependencies: ["dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/dialog-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/dialog-demo")), files: ["registry/new-york/example/dialog-demo.tsx"], }, "dialog-close-button": { name: "dialog-close-button", type: "components:example", - registryDependencies: ["dialog", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/dialog-close-button") - ), + registryDependencies: ["dialog","button"], + component: React.lazy(() => import("@/registry/new-york/example/dialog-close-button")), files: ["registry/new-york/example/dialog-close-button.tsx"], }, "drawer-demo": { name: "drawer-demo", type: "components:example", registryDependencies: ["drawer"], - component: React.lazy( - () => import("@/registry/new-york/example/drawer-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/drawer-demo")), files: ["registry/new-york/example/drawer-demo.tsx"], }, "drawer-dialog": { name: "drawer-dialog", type: "components:example", - registryDependencies: ["drawer", "dialog"], - component: React.lazy( - () => import("@/registry/new-york/example/drawer-dialog") - ), + registryDependencies: ["drawer","dialog"], + component: React.lazy(() => import("@/registry/new-york/example/drawer-dialog")), files: ["registry/new-york/example/drawer-dialog.tsx"], }, "dropdown-menu-demo": { name: "dropdown-menu-demo", type: "components:example", registryDependencies: ["dropdown-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-demo")), files: ["registry/new-york/example/dropdown-menu-demo.tsx"], }, "dropdown-menu-checkboxes": { name: "dropdown-menu-checkboxes", type: "components:example", - registryDependencies: ["dropdown-menu", "checkbox"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-checkboxes") - ), + registryDependencies: ["dropdown-menu","checkbox"], + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-checkboxes")), files: ["registry/new-york/example/dropdown-menu-checkboxes.tsx"], }, "dropdown-menu-radio-group": { name: "dropdown-menu-radio-group", type: "components:example", - registryDependencies: ["dropdown-menu", "radio-group"], - component: React.lazy( - () => import("@/registry/new-york/example/dropdown-menu-radio-group") - ), + registryDependencies: ["dropdown-menu","radio-group"], + component: React.lazy(() => import("@/registry/new-york/example/dropdown-menu-radio-group")), files: ["registry/new-york/example/dropdown-menu-radio-group.tsx"], }, "hover-card-demo": { name: "hover-card-demo", type: "components:example", registryDependencies: ["hover-card"], - component: React.lazy( - () => import("@/registry/new-york/example/hover-card-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/hover-card-demo")), files: ["registry/new-york/example/hover-card-demo.tsx"], }, "input-demo": { name: "input-demo", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-demo")), files: ["registry/new-york/example/input-demo.tsx"], }, "input-disabled": { name: "input-disabled", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-disabled")), files: ["registry/new-york/example/input-disabled.tsx"], }, "input-file": { name: "input-file", type: "components:example", registryDependencies: ["input"], - component: React.lazy( - () => import("@/registry/new-york/example/input-file") - ), + component: React.lazy(() => import("@/registry/new-york/example/input-file")), files: ["registry/new-york/example/input-file.tsx"], }, "input-form": { name: "input-form", type: "components:example", - registryDependencies: ["input", "button", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/input-form") - ), + registryDependencies: ["input","button","form"], + component: React.lazy(() => import("@/registry/new-york/example/input-form")), files: ["registry/new-york/example/input-form.tsx"], }, "input-with-button": { name: "input-with-button", type: "components:example", - registryDependencies: ["input", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-button") - ), + registryDependencies: ["input","button"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-button")), files: ["registry/new-york/example/input-with-button.tsx"], }, "input-with-label": { name: "input-with-label", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-label") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-label")), files: ["registry/new-york/example/input-with-label.tsx"], }, "input-with-text": { name: "input-with-text", type: "components:example", - registryDependencies: ["input", "button", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/input-with-text") - ), + registryDependencies: ["input","button","label"], + component: React.lazy(() => import("@/registry/new-york/example/input-with-text")), files: ["registry/new-york/example/input-with-text.tsx"], }, "label-demo": { name: "label-demo", type: "components:example", registryDependencies: ["label"], - component: React.lazy( - () => import("@/registry/new-york/example/label-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/label-demo")), files: ["registry/new-york/example/label-demo.tsx"], }, "menubar-demo": { name: "menubar-demo", type: "components:example", registryDependencies: ["menubar"], - component: React.lazy( - () => import("@/registry/new-york/example/menubar-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/menubar-demo")), files: ["registry/new-york/example/menubar-demo.tsx"], }, "navigation-menu-demo": { name: "navigation-menu-demo", type: "components:example", registryDependencies: ["navigation-menu"], - component: React.lazy( - () => import("@/registry/new-york/example/navigation-menu-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/navigation-menu-demo")), files: ["registry/new-york/example/navigation-menu-demo.tsx"], }, "pagination-demo": { name: "pagination-demo", type: "components:example", registryDependencies: ["pagination"], - component: React.lazy( - () => import("@/registry/new-york/example/pagination-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/pagination-demo")), files: ["registry/new-york/example/pagination-demo.tsx"], }, "popover-demo": { name: "popover-demo", type: "components:example", registryDependencies: ["popover"], - component: React.lazy( - () => import("@/registry/new-york/example/popover-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/popover-demo")), files: ["registry/new-york/example/popover-demo.tsx"], }, "progress-demo": { name: "progress-demo", type: "components:example", registryDependencies: ["progress"], - component: React.lazy( - () => import("@/registry/new-york/example/progress-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/progress-demo")), files: ["registry/new-york/example/progress-demo.tsx"], }, "radio-group-demo": { name: "radio-group-demo", type: "components:example", registryDependencies: ["radio-group"], - component: React.lazy( - () => import("@/registry/new-york/example/radio-group-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/radio-group-demo")), files: ["registry/new-york/example/radio-group-demo.tsx"], }, "radio-group-form": { name: "radio-group-form", type: "components:example", - registryDependencies: ["radio-group", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/radio-group-form") - ), + registryDependencies: ["radio-group","form"], + component: React.lazy(() => import("@/registry/new-york/example/radio-group-form")), files: ["registry/new-york/example/radio-group-form.tsx"], }, "resizable-demo": { name: "resizable-demo", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-demo")), files: ["registry/new-york/example/resizable-demo.tsx"], }, "resizable-demo-with-handle": { name: "resizable-demo-with-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-demo-with-handle") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-demo-with-handle")), files: ["registry/new-york/example/resizable-demo-with-handle.tsx"], }, "resizable-vertical": { name: "resizable-vertical", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-vertical") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-vertical")), files: ["registry/new-york/example/resizable-vertical.tsx"], }, "resizable-handle": { name: "resizable-handle", type: "components:example", registryDependencies: ["resizable"], - component: React.lazy( - () => import("@/registry/new-york/example/resizable-handle") - ), + component: React.lazy(() => import("@/registry/new-york/example/resizable-handle")), files: ["registry/new-york/example/resizable-handle.tsx"], }, "scroll-area-demo": { name: "scroll-area-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/new-york/example/scroll-area-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/scroll-area-demo")), files: ["registry/new-york/example/scroll-area-demo.tsx"], }, "scroll-area-horizontal-demo": { name: "scroll-area-horizontal-demo", type: "components:example", registryDependencies: ["scroll-area"], - component: React.lazy( - () => import("@/registry/new-york/example/scroll-area-horizontal-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/scroll-area-horizontal-demo")), files: ["registry/new-york/example/scroll-area-horizontal-demo.tsx"], }, "select-demo": { name: "select-demo", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-demo")), files: ["registry/new-york/example/select-demo.tsx"], }, "select-scrollable": { name: "select-scrollable", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-scrollable") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-scrollable")), files: ["registry/new-york/example/select-scrollable.tsx"], }, "select-form": { name: "select-form", type: "components:example", registryDependencies: ["select"], - component: React.lazy( - () => import("@/registry/new-york/example/select-form") - ), + component: React.lazy(() => import("@/registry/new-york/example/select-form")), files: ["registry/new-york/example/select-form.tsx"], }, "separator-demo": { name: "separator-demo", type: "components:example", registryDependencies: ["separator"], - component: React.lazy( - () => import("@/registry/new-york/example/separator-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/separator-demo")), files: ["registry/new-york/example/separator-demo.tsx"], }, "sheet-demo": { name: "sheet-demo", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/new-york/example/sheet-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/sheet-demo")), files: ["registry/new-york/example/sheet-demo.tsx"], }, "sheet-side": { name: "sheet-side", type: "components:example", registryDependencies: ["sheet"], - component: React.lazy( - () => import("@/registry/new-york/example/sheet-side") - ), + component: React.lazy(() => import("@/registry/new-york/example/sheet-side")), files: ["registry/new-york/example/sheet-side.tsx"], }, "skeleton-demo": { name: "skeleton-demo", type: "components:example", registryDependencies: ["skeleton"], - component: React.lazy( - () => import("@/registry/new-york/example/skeleton-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/skeleton-demo")), files: ["registry/new-york/example/skeleton-demo.tsx"], }, "slider-demo": { name: "slider-demo", type: "components:example", registryDependencies: ["slider"], - component: React.lazy( - () => import("@/registry/new-york/example/slider-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/slider-demo")), files: ["registry/new-york/example/slider-demo.tsx"], }, "sonner-demo": { name: "sonner-demo", type: "components:example", registryDependencies: ["sonner"], - component: React.lazy( - () => import("@/registry/new-york/example/sonner-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/sonner-demo")), files: ["registry/new-york/example/sonner-demo.tsx"], }, "stepper-demo": { name: "stepper-demo", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-demo")), files: ["registry/new-york/example/stepper-demo.tsx"], }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-orientation") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-orientation")), files: ["registry/new-york/example/stepper-orientation.tsx"], }, "stepper-description": { name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-description") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-description")), files: ["registry/new-york/example/stepper-description.tsx"], }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-custom-icons") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-icons")), files: ["registry/new-york/example/stepper-custom-icons.tsx"], }, "stepper-label-orientation": { name: "stepper-label-orientation", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-label-orientation") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-label-orientation")), files: ["registry/new-york/example/stepper-label-orientation.tsx"], }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-clickable-steps") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-clickable-steps")), files: ["registry/new-york/example/stepper-clickable-steps.tsx"], }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-optional-steps") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-optional-steps")), files: ["registry/new-york/example/stepper-optional-steps.tsx"], }, "stepper-status": { name: "stepper-status", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy( - () => import("@/registry/new-york/example/stepper-status") - ), + component: React.lazy(() => import("@/registry/new-york/example/stepper-status")), files: ["registry/new-york/example/stepper-status.tsx"], }, "switch-demo": { name: "switch-demo", type: "components:example", registryDependencies: ["switch"], - component: React.lazy( - () => import("@/registry/new-york/example/switch-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/switch-demo")), files: ["registry/new-york/example/switch-demo.tsx"], }, "switch-form": { name: "switch-form", type: "components:example", - registryDependencies: ["switch", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/switch-form") - ), + registryDependencies: ["switch","form"], + component: React.lazy(() => import("@/registry/new-york/example/switch-form")), files: ["registry/new-york/example/switch-form.tsx"], }, "table-demo": { name: "table-demo", type: "components:example", registryDependencies: ["table"], - component: React.lazy( - () => import("@/registry/new-york/example/table-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/table-demo")), files: ["registry/new-york/example/table-demo.tsx"], }, "tabs-demo": { name: "tabs-demo", type: "components:example", registryDependencies: ["tabs"], - component: React.lazy( - () => import("@/registry/new-york/example/tabs-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/tabs-demo")), files: ["registry/new-york/example/tabs-demo.tsx"], }, "textarea-demo": { name: "textarea-demo", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/textarea-demo")), files: ["registry/new-york/example/textarea-demo.tsx"], }, "textarea-disabled": { name: "textarea-disabled", type: "components:example", registryDependencies: ["textarea"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/textarea-disabled")), files: ["registry/new-york/example/textarea-disabled.tsx"], }, "textarea-form": { name: "textarea-form", type: "components:example", - registryDependencies: ["textarea", "form"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-form") - ), + registryDependencies: ["textarea","form"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-form")), files: ["registry/new-york/example/textarea-form.tsx"], }, "textarea-with-button": { name: "textarea-with-button", type: "components:example", - registryDependencies: ["textarea", "button"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-button") - ), + registryDependencies: ["textarea","button"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-button")), files: ["registry/new-york/example/textarea-with-button.tsx"], }, "textarea-with-label": { name: "textarea-with-label", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-label") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-label")), files: ["registry/new-york/example/textarea-with-label.tsx"], }, "textarea-with-text": { name: "textarea-with-text", type: "components:example", - registryDependencies: ["textarea", "label"], - component: React.lazy( - () => import("@/registry/new-york/example/textarea-with-text") - ), + registryDependencies: ["textarea","label"], + component: React.lazy(() => import("@/registry/new-york/example/textarea-with-text")), files: ["registry/new-york/example/textarea-with-text.tsx"], }, "toast-demo": { name: "toast-demo", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-demo")), files: ["registry/new-york/example/toast-demo.tsx"], }, "toast-destructive": { name: "toast-destructive", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-destructive") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-destructive")), files: ["registry/new-york/example/toast-destructive.tsx"], }, "toast-simple": { name: "toast-simple", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-simple") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-simple")), files: ["registry/new-york/example/toast-simple.tsx"], }, "toast-with-action": { name: "toast-with-action", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-with-action") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-with-action")), files: ["registry/new-york/example/toast-with-action.tsx"], }, "toast-with-title": { name: "toast-with-title", type: "components:example", registryDependencies: ["toast"], - component: React.lazy( - () => import("@/registry/new-york/example/toast-with-title") - ), + component: React.lazy(() => import("@/registry/new-york/example/toast-with-title")), files: ["registry/new-york/example/toast-with-title.tsx"], }, "toggle-group-demo": { name: "toggle-group-demo", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-demo")), files: ["registry/new-york/example/toggle-group-demo.tsx"], }, "toggle-group-disabled": { name: "toggle-group-disabled", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-disabled")), files: ["registry/new-york/example/toggle-group-disabled.tsx"], }, "toggle-group-lg": { name: "toggle-group-lg", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-lg") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-lg")), files: ["registry/new-york/example/toggle-group-lg.tsx"], }, "toggle-group-outline": { name: "toggle-group-outline", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-outline")), files: ["registry/new-york/example/toggle-group-outline.tsx"], }, "toggle-group-sm": { name: "toggle-group-sm", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-sm") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-sm")), files: ["registry/new-york/example/toggle-group-sm.tsx"], }, "toggle-group-single": { name: "toggle-group-single", type: "components:example", registryDependencies: ["toggle-group"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-group-single") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-group-single")), files: ["registry/new-york/example/toggle-group-single.tsx"], }, "toggle-demo": { name: "toggle-demo", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-demo")), files: ["registry/new-york/example/toggle-demo.tsx"], }, "toggle-disabled": { name: "toggle-disabled", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-disabled") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-disabled")), files: ["registry/new-york/example/toggle-disabled.tsx"], }, "toggle-lg": { name: "toggle-lg", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-lg") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-lg")), files: ["registry/new-york/example/toggle-lg.tsx"], }, "toggle-outline": { name: "toggle-outline", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-outline") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-outline")), files: ["registry/new-york/example/toggle-outline.tsx"], }, "toggle-sm": { name: "toggle-sm", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-sm") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-sm")), files: ["registry/new-york/example/toggle-sm.tsx"], }, "toggle-with-text": { name: "toggle-with-text", type: "components:example", registryDependencies: ["toggle"], - component: React.lazy( - () => import("@/registry/new-york/example/toggle-with-text") - ), + component: React.lazy(() => import("@/registry/new-york/example/toggle-with-text")), files: ["registry/new-york/example/toggle-with-text.tsx"], }, "tooltip-demo": { name: "tooltip-demo", type: "components:example", registryDependencies: ["tooltip"], - component: React.lazy( - () => import("@/registry/new-york/example/tooltip-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/tooltip-demo")), files: ["registry/new-york/example/tooltip-demo.tsx"], }, "typography-blockquote": { name: "typography-blockquote", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-blockquote") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-blockquote")), files: ["registry/new-york/example/typography-blockquote.tsx"], }, "typography-demo": { name: "typography-demo", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-demo") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-demo")), files: ["registry/new-york/example/typography-demo.tsx"], }, "typography-h1": { name: "typography-h1", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h1") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h1")), files: ["registry/new-york/example/typography-h1.tsx"], }, "typography-h2": { name: "typography-h2", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h2") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h2")), files: ["registry/new-york/example/typography-h2.tsx"], }, "typography-h3": { name: "typography-h3", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h3") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h3")), files: ["registry/new-york/example/typography-h3.tsx"], }, "typography-h4": { name: "typography-h4", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-h4") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-h4")), files: ["registry/new-york/example/typography-h4.tsx"], }, "typography-inline-code": { name: "typography-inline-code", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-inline-code") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-inline-code")), files: ["registry/new-york/example/typography-inline-code.tsx"], }, "typography-large": { name: "typography-large", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-large") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-large")), files: ["registry/new-york/example/typography-large.tsx"], }, "typography-lead": { name: "typography-lead", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-lead") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-lead")), files: ["registry/new-york/example/typography-lead.tsx"], }, "typography-list": { name: "typography-list", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-list") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-list")), files: ["registry/new-york/example/typography-list.tsx"], }, "typography-muted": { name: "typography-muted", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-muted") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-muted")), files: ["registry/new-york/example/typography-muted.tsx"], }, "typography-p": { name: "typography-p", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-p") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-p")), files: ["registry/new-york/example/typography-p.tsx"], }, "typography-small": { name: "typography-small", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-small") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-small")), files: ["registry/new-york/example/typography-small.tsx"], }, "typography-table": { name: "typography-table", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/typography-table") - ), + component: React.lazy(() => import("@/registry/new-york/example/typography-table")), files: ["registry/new-york/example/typography-table.tsx"], }, "mode-toggle": { name: "mode-toggle", type: "components:example", registryDependencies: undefined, - component: React.lazy( - () => import("@/registry/new-york/example/mode-toggle") - ), + component: React.lazy(() => import("@/registry/new-york/example/mode-toggle")), files: ["registry/new-york/example/mode-toggle.tsx"], }, - cards: { + "cards": { name: "cards", type: "components:example", registryDependencies: undefined, diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index 344f41cb617..b5f974aee09 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** StepperContext **********/\n\ninterface StepperContextValue extends StepperProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepperContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepperContext)\n\nexport const StepperProvider: React.FC<{\n value: StepperContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Stepper **********/\n\nexport interface StepperProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Stepper = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nStepper.displayName = \"Stepper\"\n\n/********** StepperItem **********/\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepperConfig extends StepperItemLabelProps {\n icon?: React.ReactElement\n}\n\ninterface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepperConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepperItemStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\nexport interface StepperItemProps extends StepProps, StepperItemStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const StepperItem = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStepperItem.displayName = \"StepperItem\"\n\n/********** StepperItemLabel **********/\n\ninterface StepperItemLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\n\nconst StepperItemLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepperItemLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n \n {description}\n

\n )}\n
\n ) : null\n}\n\nStepperItemLabel.displayName = \"StepperItemLabel\"\n\n/********** StepperItemConnector **********/\n\ninterface StepperItemConnectorProps\n extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst StepperItemConnector = React.memo(\n ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nStepperItemConnector.displayName = \"StepperItemConnector\"\n" + "content": "import React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\n\ninterface StepperProps {\n steps: {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n icon?: React.ReactNode\n optional?: boolean\n }[]\n initialStep: number\n orientation?: \"vertical\" | \"horizontal\"\n labelOrientation?: \"vertical\" | \"horizontal\"\n scrollTracking?: boolean\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n status?: \"default\" | \"success\" | \"error\" | \"loading\"\n isClickable?: boolean\n}\n\ninterface ContextStepperProps extends StepperProps {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n}\n\nconst StepperContext = React.createContext({\n steps: [],\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n activeStep: 0,\n})\n\nconst StepperProvider = ({\n value,\n children,\n}: {\n value: StepperProps\n children: React.ReactNode\n}) => {\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\nexport function useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const isDisabledStep = context.activeStep === 0\n const isLastStep = context.activeStep === context.steps.length - 1\n const isOptionalStep = context.steps[context.activeStep]?.optional\n const isFinished = context.activeStep === context.steps.length\n\n return {\n ...context,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n isFinished,\n }\n}\n\nexport const Stepper = React.forwardRef<\n HTMLDivElement,\n StepperProps & React.HTMLAttributes\n>(\n (\n {\n initialStep,\n steps,\n status = \"default\",\n orientation = \"horizontal\",\n labelOrientation = \"horizontal\",\n scrollTracking = false,\n children,\n variant = \"default\",\n isClickable = true,\n className,\n ...props\n },\n ref\n ) => {\n const footer = [] as React.ReactElement[]\n\n const items = React.Children.toArray(children).map((child, index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type !== StepperItem && child.type !== StepperFooter) {\n throw new Error(\n \"Stepper children must be either or .\"\n )\n }\n if (child.type === StepperFooter) {\n footer.push(child)\n return null\n }\n const stepperItemProps = {\n ...child.props,\n step: index,\n }\n\n return React.cloneElement(child, stepperItemProps)\n })\n\n return (\n \n
\n \n {items}\n
\n {orientation === \"horizontal\" && (\n {children}\n )}\n {footer}\n
\n \n )\n }\n)\n\nconst HorizontalContent = ({ children }: { children?: React.ReactNode }) => {\n const { activeStep, isFinished } = useStepper()\n\n if (isFinished) {\n return null\n }\n\n const activeStepperItem = React.Children.toArray(children)[\n activeStep\n ] as React.ReactElement\n\n const content = activeStepperItem?.props?.children\n\n return content\n}\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nconst icons = {\n success: ,\n error: ,\n loading: ,\n default: null,\n} as const\n\nexport const StepperItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes & {\n onStepperItemClick?: (\n e: React.MouseEvent\n ) => void\n }\n // @ts-ignore - step is a prop that is added from the Stepper through React.Children.\n>(({ step, children, className, onStepperItemClick, ...props }, ref) => {\n const {\n activeStep,\n setStep,\n steps,\n orientation,\n labelOrientation,\n scrollTracking,\n variant,\n status,\n isClickable,\n } = useStepper()\n\n const isActive = step === activeStep\n const isCompleted = step < activeStep\n const isDisabled = step > activeStep && !isClickable\n\n const isLastStep = step === steps.length - 1\n\n const isVertical = orientation === \"vertical\"\n const isVerticalLabel = labelOrientation === \"vertical\"\n\n const isError = isActive && status === \"error\"\n\n let icon = steps[step].icon || step + 1\n if (status !== \"default\" && isActive) {\n icon = icons[status!]\n }\n if (isCompleted) {\n icon = icons.success\n }\n\n const content = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type !== StepperFooter\n )\n\n const footer = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type === StepperFooter\n )[0] as React.ReactElement\n\n const onClickItem = (e: React.MouseEvent) => {\n if (isDisabled) {\n return\n }\n if (onStepperItemClick) {\n return onStepperItemClick(e)\n }\n setStep(step)\n }\n\n return (\n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n >\n \n {icon}\n \n \n

\n {steps[step].label}\n

\n {steps[step].description && (\n

\n {steps[step].description}\n

\n )}\n
\n
\n
\n {!isLastStep && (\n \n )}\n {isVertical && isActive && (\n
\n {content}\n {footer}\n
\n )}\n
\n
\n )\n})\n\nStepperItem.displayName = \"StepperItem\"\n\nexport const StepperFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n return (\n
\n {children}\n
\n )\n})\n\nStepperFooter.displayName = \"StepperFooter\"\n" }, { "name": "use-stepper.ts", From 9f1a2ef3d38c6f889a03e6392b05f3579643bc3c Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 17 Jan 2024 00:46:41 -0300 Subject: [PATCH 14/62] chore: remove unused files --- apps/www/registry/default/ui/use-stepper.ts | 124 ------------------ apps/www/registry/new-york/ui/use-stepper.tsx | 21 --- 2 files changed, 145 deletions(-) delete mode 100644 apps/www/registry/default/ui/use-stepper.ts delete mode 100644 apps/www/registry/new-york/ui/use-stepper.tsx diff --git a/apps/www/registry/default/ui/use-stepper.ts b/apps/www/registry/default/ui/use-stepper.ts deleted file mode 100644 index 56f6cc81b23..00000000000 --- a/apps/www/registry/default/ui/use-stepper.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as React from "react" - -import { StepperItemProps } from "./stepper" - -type useStepper = { - initialStep: number - steps: Pick< - StepperItemProps, - "label" | "description" | "optional" | "optionalLabel" | "icon" - >[] -} - -type useStepperReturn = { - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void - activeStep: number - isDisabledStep: boolean - isLastStep: boolean - isOptionalStep: boolean | undefined -} - -export function useStepper({ - initialStep, - steps, -}: useStepper): useStepperReturn { - const [activeStep, setActiveStep] = React.useState(initialStep) - - const nextStep = () => { - setActiveStep((prev) => prev + 1) - } - - const prevStep = () => { - setActiveStep((prev) => prev - 1) - } - - const resetSteps = () => { - setActiveStep(initialStep) - } - - const setStep = (step: number) => { - setActiveStep(step) - } - - const isDisabledStep = activeStep === 0 - - const isLastStep = activeStep === steps.length - 1 - - const isOptionalStep = steps[activeStep]?.optional - - return { - nextStep, - prevStep, - resetSteps, - setStep, - activeStep, - isDisabledStep, - isLastStep, - isOptionalStep, - } -} - -interface UseMediaQueryOptions { - getInitialValueInEffect: boolean -} - -type MediaQueryCallback = (event: { matches: boolean; media: string }) => void - -/** - * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia - * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent - * */ -function attachMediaListener( - query: MediaQueryList, - callback: MediaQueryCallback -) { - try { - query.addEventListener("change", callback) - return () => query.removeEventListener("change", callback) - } catch (e) { - query.addListener(callback) - return () => query.removeListener(callback) - } -} - -function getInitialValue(query: string, initialValue?: boolean) { - if (typeof initialValue === "boolean") { - return initialValue - } - - if (typeof window !== "undefined" && "matchMedia" in window) { - return window.matchMedia(query).matches - } - - return false -} - -export function useMediaQuery( - query: string, - initialValue?: boolean, - { getInitialValueInEffect }: UseMediaQueryOptions = { - getInitialValueInEffect: true, - } -) { - const [matches, setMatches] = React.useState( - getInitialValueInEffect ? false : getInitialValue(query, initialValue) - ) - const queryRef = React.useRef() - - React.useEffect(() => { - if ("matchMedia" in window) { - queryRef.current = window.matchMedia(query) - setMatches(queryRef.current.matches) - return attachMediaListener(queryRef.current, (event) => - setMatches(event.matches) - ) - } - - return undefined - }, [query]) - - return matches -} diff --git a/apps/www/registry/new-york/ui/use-stepper.tsx b/apps/www/registry/new-york/ui/use-stepper.tsx deleted file mode 100644 index a95dcfa0701..00000000000 --- a/apps/www/registry/new-york/ui/use-stepper.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as React from "react" - -type UseStepper = { - steps: { - label: string | React.ReactNode - description?: string | React.ReactNode - optional?: boolean - }[] - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void - activeStep: number - isDisabledStep: boolean - isLastStep: boolean - isOptionalStep: boolean | undefined -} - -export function useStepper() { - return null -} From 4b012243f14cc80d96e12eb2050788eeec79c743 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 17 Jan 2024 00:51:22 -0300 Subject: [PATCH 15/62] chore: update docs --- apps/www/content/docs/components/stepper.mdx | 62 -------------------- 1 file changed, 62 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 841fc47215d..17e5af35869 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -24,36 +24,6 @@ component: true npx shadcn-ui@latest add stepper ``` -Use the Stepper component with useStepper hook - -```tsx title="page.tsx" -"use client" - -import { useStepper } from "@/components/ui/use-stepper" - -export default function Page() { - return ( - - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} - -
- ) -} - -function Child() { - const { currentStep, nextStep, prevStep, ...rest } = useStepper() - return ( - // ... - ) -} -``` - @@ -64,42 +34,10 @@ function Child() { Copy and paste the following code into your project. -`stepper.tsx` - Update the import paths to match your project setup. -Use the Stepper component with useStepper hook - -```tsx title="page.tsx" -"use client" - -import { useStepper } from "@/components/ui/use-stepper" - -export default function Page() { - return ( - - {steps.map((step, index) => ( - -
-

Step {index + 1} content

-
-
- ))} - -
- ) -} - -function Child() { - const { currentStep, nextStep, prevStep, ...rest } = useStepper() - return ( - // ... - ) -} -``` - From f54a4a6cebc805be9f944ab83e1d34524d11adc8 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 22 Jan 2024 00:06:08 -0300 Subject: [PATCH 16/62] docs: add stepper with form example --- apps/www/__registry__/index.tsx | 18 +- apps/www/content/docs/components/stepper.mdx | 11 + apps/www/public/registry/index.json | 6 +- .../registry/styles/default/carousel.json | 3 + .../registry/styles/default/stepper.json | 6 +- .../registry/styles/new-york/carousel.json | 3 + .../registry/styles/new-york/stepper.json | 6 +- .../registry/default/example/stepper-form.tsx | 190 ++++++++++++++++++ apps/www/registry/default/ui/stepper.tsx | 5 - .../new-york/example/stepper-form.tsx | 190 ++++++++++++++++++ apps/www/registry/new-york/ui/stepper.tsx | 12 +- apps/www/registry/registry.ts | 8 +- 12 files changed, 433 insertions(+), 25 deletions(-) create mode 100644 apps/www/registry/default/example/stepper-form.tsx create mode 100644 apps/www/registry/new-york/example/stepper-form.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index cc977405663..18447863f17 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -255,7 +255,7 @@ export const Index: Record = { type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/stepper")), - files: ["registry/default/ui/stepper.tsx","registry/default/ui/use-stepper.ts"], + files: ["registry/default/ui/stepper.tsx"], }, "switch": { name: "switch", @@ -943,6 +943,13 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/example/stepper-description")), files: ["registry/default/example/stepper-description.tsx"], }, + "stepper-form": { + name: "stepper-form", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-form")), + files: ["registry/default/example/stepper-form.tsx"], + }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", @@ -1537,7 +1544,7 @@ export const Index: Record = { type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/stepper")), - files: ["registry/new-york/ui/stepper.tsx","registry/new-york/ui/use-stepper.ts"], + files: ["registry/new-york/ui/stepper.tsx"], }, "switch": { name: "switch", @@ -2225,6 +2232,13 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/example/stepper-description")), files: ["registry/new-york/example/stepper-description.tsx"], }, + "stepper-form": { + name: "stepper-form", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-form")), + files: ["registry/new-york/example/stepper-form.tsx"], + }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 17e5af35869..4e4b44001b1 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -152,6 +152,7 @@ function useStepper(): StepperProps & { prevStep: () => void resetSteps: () => void setStep: (step: number) => void + setStatus: (status: "default" | "success" | "error" | "loading") => void activeStep: number isDisabledStep: boolean isLastStep: boolean @@ -267,3 +268,13 @@ function MyStepperFooter() { ### Scroll tracking If you would like the stepper to scroll to the active step when it is not in view you can do so using the `scrollTracking` prop on the Stepper component. + +### With Forms + +If you want to use the stepper with forms, you can do so by using the `useStepper` hook to control the component. + +This example uses the `Form` component of shadcn and the `react-hook-form` hooks to create a form with zod for validations. + +You can also use the component with server actions. + + diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index 9c1e0c777b1..e119304b4c9 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -92,6 +92,9 @@ "dependencies": [ "embla-carousel-react" ], + "devDependencies": [ + "embla-carousel" + ], "registryDependencies": [ "button" ], @@ -360,8 +363,7 @@ { "name": "stepper", "files": [ - "ui/stepper.tsx", - "ui/use-stepper.ts" + "ui/stepper.tsx" ], "type": "components:ui" }, diff --git a/apps/www/public/registry/styles/default/carousel.json b/apps/www/public/registry/styles/default/carousel.json index b405bd00639..9390ce59dec 100644 --- a/apps/www/public/registry/styles/default/carousel.json +++ b/apps/www/public/registry/styles/default/carousel.json @@ -3,6 +3,9 @@ "dependencies": [ "embla-carousel-react" ], + "devDependencies": [ + "embla-carousel" + ], "registryDependencies": [ "button" ], diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index b5f974aee09..09c83d23444 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,11 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "import React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\n\ninterface StepperProps {\n steps: {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n icon?: React.ReactNode\n optional?: boolean\n }[]\n initialStep: number\n orientation?: \"vertical\" | \"horizontal\"\n labelOrientation?: \"vertical\" | \"horizontal\"\n scrollTracking?: boolean\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n status?: \"default\" | \"success\" | \"error\" | \"loading\"\n isClickable?: boolean\n}\n\ninterface ContextStepperProps extends StepperProps {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n}\n\nconst StepperContext = React.createContext({\n steps: [],\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n activeStep: 0,\n})\n\nconst StepperProvider = ({\n value,\n children,\n}: {\n value: StepperProps\n children: React.ReactNode\n}) => {\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\nexport function useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const isDisabledStep = context.activeStep === 0\n const isLastStep = context.activeStep === context.steps.length - 1\n const isOptionalStep = context.steps[context.activeStep]?.optional\n const isFinished = context.activeStep === context.steps.length\n\n return {\n ...context,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n isFinished,\n }\n}\n\nexport const Stepper = React.forwardRef<\n HTMLDivElement,\n StepperProps & React.HTMLAttributes\n>(\n (\n {\n initialStep,\n steps,\n status = \"default\",\n orientation = \"horizontal\",\n labelOrientation = \"horizontal\",\n scrollTracking = false,\n children,\n variant = \"default\",\n isClickable = true,\n className,\n ...props\n },\n ref\n ) => {\n const footer = [] as React.ReactElement[]\n\n const items = React.Children.toArray(children).map((child, index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type !== StepperItem && child.type !== StepperFooter) {\n throw new Error(\n \"Stepper children must be either or .\"\n )\n }\n if (child.type === StepperFooter) {\n footer.push(child)\n return null\n }\n const stepperItemProps = {\n ...child.props,\n step: index,\n }\n\n return React.cloneElement(child, stepperItemProps)\n })\n\n return (\n \n
\n \n {items}\n
\n {orientation === \"horizontal\" && (\n {children}\n )}\n {footer}\n
\n \n )\n }\n)\n\nconst HorizontalContent = ({ children }: { children?: React.ReactNode }) => {\n const { activeStep, isFinished } = useStepper()\n\n if (isFinished) {\n return null\n }\n\n const activeStepperItem = React.Children.toArray(children)[\n activeStep\n ] as React.ReactElement\n\n const content = activeStepperItem?.props?.children\n\n return content\n}\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nconst icons = {\n success: ,\n error: ,\n loading: ,\n default: null,\n} as const\n\nexport const StepperItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes & {\n onStepperItemClick?: (\n e: React.MouseEvent\n ) => void\n }\n // @ts-ignore - step is a prop that is added from the Stepper through React.Children.\n>(({ step, children, className, onStepperItemClick, ...props }, ref) => {\n const {\n activeStep,\n setStep,\n steps,\n orientation,\n labelOrientation,\n scrollTracking,\n variant,\n status,\n isClickable,\n } = useStepper()\n\n const isActive = step === activeStep\n const isCompleted = step < activeStep\n const isDisabled = step > activeStep && !isClickable\n\n const isLastStep = step === steps.length - 1\n\n const isVertical = orientation === \"vertical\"\n const isVerticalLabel = labelOrientation === \"vertical\"\n\n const isError = isActive && status === \"error\"\n\n let icon = steps[step].icon || step + 1\n if (status !== \"default\" && isActive) {\n icon = icons[status!]\n }\n if (isCompleted) {\n icon = icons.success\n }\n\n const content = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type !== StepperFooter\n )\n\n const footer = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type === StepperFooter\n )[0] as React.ReactElement\n\n const onClickItem = (e: React.MouseEvent) => {\n if (isDisabled) {\n return\n }\n if (onStepperItemClick) {\n return onStepperItemClick(e)\n }\n setStep(step)\n }\n\n return (\n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n >\n \n {icon}\n \n \n

\n {steps[step].label}\n

\n {steps[step].description && (\n

\n {steps[step].description}\n

\n )}\n
\n
\n
\n {!isLastStep && (\n \n )}\n {isVertical && isActive && (\n
\n {content}\n {footer}\n
\n )}\n
\n
\n )\n})\n\nStepperItem.displayName = \"StepperItem\"\n\nexport const StepperFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n return (\n
\n {children}\n
\n )\n})\n\nStepperFooter.displayName = \"StepperFooter\"\n" - }, - { - "name": "use-stepper.ts", - "content": "import * as React from \"react\"\n\nimport { StepperItemProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepperItemProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" + "content": "import React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\n\ninterface StepperProps {\n steps: {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n icon?: React.ReactNode\n optional?: boolean\n }[]\n initialStep: number\n orientation?: \"vertical\" | \"horizontal\"\n labelOrientation?: \"vertical\" | \"horizontal\"\n scrollTracking?: boolean\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n status?: \"default\" | \"success\" | \"error\" | \"loading\"\n isClickable?: boolean\n}\n\ninterface ContextStepperProps extends StepperProps {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n}\n\nconst StepperContext = React.createContext({\n steps: [],\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n activeStep: 0,\n})\n\nconst StepperProvider = ({\n value,\n children,\n}: {\n value: StepperProps\n children: React.ReactNode\n}) => {\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\nexport function useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const isDisabledStep = context.activeStep === 0\n const isLastStep = context.activeStep === context.steps.length - 1\n const isOptionalStep = context.steps[context.activeStep]?.optional\n const isFinished = context.activeStep === context.steps.length\n\n return {\n ...context,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n isFinished,\n }\n}\n\nexport const Stepper = React.forwardRef<\n HTMLDivElement,\n StepperProps & React.HTMLAttributes\n>(\n (\n {\n initialStep,\n steps,\n status = \"default\",\n orientation = \"horizontal\",\n labelOrientation = \"horizontal\",\n scrollTracking = false,\n children,\n variant = \"default\",\n isClickable = true,\n className,\n ...props\n },\n ref\n ) => {\n const footer = [] as React.ReactElement[]\n\n const items = React.Children.toArray(children).map((child, index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === StepperFooter) {\n footer.push(child)\n return null\n }\n const stepperItemProps = {\n ...child.props,\n step: index,\n }\n\n return React.cloneElement(child, stepperItemProps)\n })\n\n return (\n \n
\n \n {items}\n
\n {orientation === \"horizontal\" && (\n {children}\n )}\n {footer}\n
\n \n )\n }\n)\n\nconst HorizontalContent = ({ children }: { children?: React.ReactNode }) => {\n const { activeStep, isFinished } = useStepper()\n\n if (isFinished) {\n return null\n }\n\n const activeStepperItem = React.Children.toArray(children)[\n activeStep\n ] as React.ReactElement\n\n const content = activeStepperItem?.props?.children\n\n return content\n}\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nconst icons = {\n success: ,\n error: ,\n loading: ,\n default: null,\n} as const\n\nexport const StepperItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes & {\n onStepperItemClick?: (\n e: React.MouseEvent\n ) => void\n }\n // @ts-ignore - step is a prop that is added from the Stepper through React.Children.\n>(({ step, children, className, onStepperItemClick, ...props }, ref) => {\n const {\n activeStep,\n setStep,\n steps,\n orientation,\n labelOrientation,\n scrollTracking,\n variant,\n status,\n isClickable,\n } = useStepper()\n\n const isActive = step === activeStep\n const isCompleted = step < activeStep\n const isDisabled = step > activeStep && !isClickable\n\n const isLastStep = step === steps.length - 1\n\n const isVertical = orientation === \"vertical\"\n const isVerticalLabel = labelOrientation === \"vertical\"\n\n const isError = isActive && status === \"error\"\n\n let icon = steps[step].icon || step + 1\n if (status !== \"default\" && isActive) {\n icon = icons[status!]\n }\n if (isCompleted) {\n icon = icons.success\n }\n\n const content = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type !== StepperFooter\n )\n\n const footer = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type === StepperFooter\n )[0] as React.ReactElement\n\n const onClickItem = (e: React.MouseEvent) => {\n if (isDisabled) {\n return\n }\n if (onStepperItemClick) {\n return onStepperItemClick(e)\n }\n setStep(step)\n }\n\n return (\n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n >\n \n {icon}\n \n \n

\n {steps[step].label}\n

\n {steps[step].description && (\n

\n {steps[step].description}\n

\n )}\n
\n
\n
\n {!isLastStep && (\n \n )}\n {isVertical && isActive && (\n
\n {content}\n {footer}\n
\n )}\n
\n
\n )\n})\n\nStepperItem.displayName = \"StepperItem\"\n\nexport const StepperFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n return (\n
\n {children}\n
\n )\n})\n\nStepperFooter.displayName = \"StepperFooter\"\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/carousel.json b/apps/www/public/registry/styles/new-york/carousel.json index 24f542a2aff..135f4c30ef8 100644 --- a/apps/www/public/registry/styles/new-york/carousel.json +++ b/apps/www/public/registry/styles/new-york/carousel.json @@ -3,6 +3,9 @@ "dependencies": [ "embla-carousel-react" ], + "devDependencies": [ + "embla-carousel" + ], "registryDependencies": [ "button" ], diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index 0aa13aad00d..68e17eafae4 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,11 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "import * as React from \"react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\nimport { useMediaQuery } from \"./use-stepper\"\n\n/********** StepperContext **********/\n\ninterface StepperContextValue extends StepperProps {\n isClickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n isLabelVertical?: boolean\n stepCount?: number\n}\n\nconst StepperContext = React.createContext({\n activeStep: 0,\n})\n\nexport const useStepperContext = () => React.useContext(StepperContext)\n\nexport const StepperProvider: React.FC<{\n value: StepperContextValue\n children: React.ReactNode\n}> = ({ value, children }) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const isVertical = value.orientation === \"vertical\"\n const isLabelVertical =\n value.orientation !== \"vertical\" && value.labelOrientation === \"vertical\"\n\n return (\n \n {children}\n \n )\n}\n\n/********** Stepper **********/\n\nexport interface StepperProps extends React.HTMLAttributes {\n activeStep: number\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n onClickStep?: (step: number) => void\n successIcon?: React.ReactElement\n errorIcon?: React.ReactElement\n labelOrientation?: \"vertical\" | \"horizontal\"\n children?: React.ReactNode\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n}\n\nexport const Stepper = React.forwardRef(\n (\n {\n activeStep = 0,\n state,\n responsive = true,\n orientation: orientationProp = \"horizontal\",\n onClickStep,\n labelOrientation = \"horizontal\",\n children,\n errorIcon,\n successIcon,\n variant = \"default\",\n className,\n ...props\n },\n ref\n ) => {\n const childArr = React.Children.toArray(children)\n\n const stepCount = childArr.length\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) return\n return React.Children.map(\n node.props.children,\n (childNode) => childNode\n )\n })\n }\n return null\n }\n\n const isClickable = !!onClickStep\n\n const isMobile = useMediaQuery(\"(max-width: 43em)\")\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n return (\n \n \n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) && child.props.isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n\n return null\n })}\n
\n {orientation === \"horizontal\" && renderHorizontalContent()}\n \n )\n }\n)\n\nStepper.displayName = \"Stepper\"\n\n/********** StepperItem **********/\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n isClickable: {\n true: \"cursor-pointer\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nexport interface StepperConfig extends StepperItemLabelProps {\n icon?: React.ReactElement\n}\n\ninterface StepProps\n extends React.HTMLAttributes,\n VariantProps,\n StepperConfig {\n isCompletedStep?: boolean\n}\n\ninterface StepperItemStatus {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n}\n\nexport interface StepperItemProps extends StepProps, StepperItemStatus {\n additionalClassName?: {\n button?: string\n label?: string\n description?: string\n }\n}\n\nexport const StepperItem = React.forwardRef(\n (props, ref) => {\n const {\n children,\n description,\n icon: CustomIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n label,\n optional,\n optionalLabel,\n className,\n additionalClassName,\n ...rest\n } = props\n\n const {\n isVertical,\n isError,\n isLoading,\n successIcon: CustomSuccessIcon,\n errorIcon: CustomErrorIcon,\n isLabelVertical,\n onClickStep,\n isClickable,\n variant,\n } = useStepperContext()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const handleClick = (index: number) => {\n if (isClickable && onClickStep) {\n onClickStep(index)\n }\n }\n\n const Icon = React.useMemo(() => CustomIcon ?? null, [CustomIcon])\n\n const Success = React.useMemo(\n () => CustomSuccessIcon ?? ,\n [CustomSuccessIcon]\n )\n\n const Error = React.useMemo(\n () => CustomErrorIcon ?? ,\n [CustomErrorIcon]\n )\n\n const RenderIcon = React.useMemo(() => {\n if (isCompletedStep) return Success\n if (isCurrentStep) {\n if (isError) return Error\n if (isLoading) return \n }\n if (Icon) return Icon\n return (index || 0) + 1\n }, [\n isCompletedStep,\n Success,\n isCurrentStep,\n Icon,\n index,\n isError,\n Error,\n isLoading,\n ])\n\n return (\n handleClick(index)}\n aria-disabled={!hasVisited}\n >\n \n \n {RenderIcon}\n \n \n
\n \n {(isCurrentStep || isCompletedStep) && children}\n \n
\n )\n }\n)\n\nStepperItem.displayName = \"StepperItem\"\n\n/********** StepperItemLabel **********/\n\ninterface StepperItemLabelProps {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n optional?: boolean\n optionalLabel?: string | React.ReactNode\n labelClassName?: string\n descriptionClassName?: string\n}\nconst StepperItemLabel = ({\n isCurrentStep,\n label,\n description,\n optional,\n optionalLabel,\n labelClassName,\n descriptionClassName,\n}: StepperItemLabelProps & {\n isCurrentStep?: boolean\n}) => {\n const { isLabelVertical } = useStepperContext()\n\n const shouldRender = !!label || !!description\n\n const renderOptionalLabel = !!optional && !!optionalLabel\n\n return shouldRender ? (\n \n {!!label && (\n

\n {label}\n {renderOptionalLabel && (\n \n ({optionalLabel})\n \n )}\n

\n )}\n {!!description && (\n \n {description}\n

\n )}\n
\n ) : null\n}\n\nStepperItemLabel.displayName = \"StepperItemLabel\"\n\n/********** StepperItemConnector **********/\n\ninterface StepperItemConnectorProps\n extends React.HTMLAttributes {\n isCompletedStep: boolean\n isLastStep?: boolean | null\n hasLabel?: boolean\n index: number\n}\n\nconst StepperItemConnector = React.memo(\n ({ isCompletedStep, children, isLastStep }: StepperItemConnectorProps) => {\n const { isVertical } = useStepperContext()\n\n if (isVertical) {\n return (\n \n {!isCompletedStep && (\n
{children}
\n )}\n
\n )\n }\n\n if (isLastStep) {\n return null\n }\n\n return (\n \n )\n }\n)\n\nStepperItemConnector.displayName = \"StepperItemConnector\"\n" - }, - { - "name": "use-stepper.ts", - "content": "import * as React from \"react\"\n\nimport { StepperItemProps } from \"./stepper\"\n\ntype useStepper = {\n initialStep: number\n steps: Pick<\n StepperItemProps,\n \"label\" | \"description\" | \"optional\" | \"optionalLabel\" | \"icon\"\n >[]\n}\n\ntype useStepperReturn = {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n isDisabledStep: boolean\n isLastStep: boolean\n isOptionalStep: boolean | undefined\n}\n\nexport function useStepper({\n initialStep,\n steps,\n}: useStepper): useStepperReturn {\n const [activeStep, setActiveStep] = React.useState(initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n const isDisabledStep = activeStep === 0\n\n const isLastStep = activeStep === steps.length - 1\n\n const isOptionalStep = steps[activeStep]?.optional\n\n return {\n nextStep,\n prevStep,\n resetSteps,\n setStep,\n activeStep,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n }\n}\n\ninterface UseMediaQueryOptions {\n getInitialValueInEffect: boolean\n}\n\ntype MediaQueryCallback = (event: { matches: boolean; media: string }) => void\n\n/**\n * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia\n * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent\n * */\nfunction attachMediaListener(\n query: MediaQueryList,\n callback: MediaQueryCallback\n) {\n try {\n query.addEventListener(\"change\", callback)\n return () => query.removeEventListener(\"change\", callback)\n } catch (e) {\n query.addListener(callback)\n return () => query.removeListener(callback)\n }\n}\n\nfunction getInitialValue(query: string, initialValue?: boolean) {\n if (typeof initialValue === \"boolean\") {\n return initialValue\n }\n\n if (typeof window !== \"undefined\" && \"matchMedia\" in window) {\n return window.matchMedia(query).matches\n }\n\n return false\n}\n\nexport function useMediaQuery(\n query: string,\n initialValue?: boolean,\n { getInitialValueInEffect }: UseMediaQueryOptions = {\n getInitialValueInEffect: true,\n }\n) {\n const [matches, setMatches] = React.useState(\n getInitialValueInEffect ? false : getInitialValue(query, initialValue)\n )\n const queryRef = React.useRef()\n\n React.useEffect(() => {\n if (\"matchMedia\" in window) {\n queryRef.current = window.matchMedia(query)\n setMatches(queryRef.current.matches)\n return attachMediaListener(queryRef.current, (event) =>\n setMatches(event.matches)\n )\n }\n\n return undefined\n }, [query])\n\n return matches\n}\n" + "content": "import React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\n\ninterface StepperProps {\n steps: {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n icon?: React.ReactNode\n optional?: boolean\n }[]\n initialStep: number\n orientation?: \"vertical\" | \"horizontal\"\n labelOrientation?: \"vertical\" | \"horizontal\"\n scrollTracking?: boolean\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n status?: \"default\" | \"success\" | \"error\" | \"loading\"\n isClickable?: boolean\n}\n\ninterface ContextStepperProps extends StepperProps {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n setStatus: (status: StepperProps[\"status\"]) => void\n}\n\nconst StepperContext = React.createContext({\n steps: [],\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n activeStep: 0,\n setStatus: () => {},\n})\n\nconst StepperProvider = ({\n value,\n children,\n}: {\n value: StepperProps\n children: React.ReactNode\n}) => {\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n const [status, setStatus] = React.useState(\n value.status\n )\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\nexport function useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const isDisabledStep = context.activeStep === 0\n const isLastStep = context.activeStep === context.steps.length - 1\n const isOptionalStep = context.steps[context.activeStep]?.optional\n const isFinished = context.activeStep === context.steps.length\n\n return {\n ...context,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n isFinished,\n }\n}\n\nexport const Stepper = React.forwardRef<\n HTMLDivElement,\n StepperProps & React.HTMLAttributes\n>(\n (\n {\n initialStep,\n steps,\n status = \"default\",\n orientation = \"horizontal\",\n labelOrientation = \"horizontal\",\n scrollTracking = false,\n children,\n variant = \"default\",\n isClickable = true,\n className,\n ...props\n },\n ref\n ) => {\n const footer = [] as React.ReactElement[]\n\n const items = React.Children.toArray(children).map((child, index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === StepperFooter) {\n footer.push(child)\n return null\n }\n const stepperItemProps = {\n ...child.props,\n step: index,\n }\n\n return React.cloneElement(child, stepperItemProps)\n })\n\n return (\n \n
\n \n {items}\n
\n {orientation === \"horizontal\" && (\n {children}\n )}\n {footer}\n
\n \n )\n }\n)\n\nconst HorizontalContent = ({ children }: { children?: React.ReactNode }) => {\n const { activeStep, isFinished } = useStepper()\n\n if (isFinished) {\n return null\n }\n\n const activeStepperItem = React.Children.toArray(children)[\n activeStep\n ] as React.ReactElement\n\n const content = activeStepperItem?.props?.children\n\n return content\n}\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nconst icons = {\n success: ,\n error: ,\n loading: ,\n default: null,\n} as const\n\nexport const StepperItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes & {\n onStepperItemClick?: (\n e: React.MouseEvent\n ) => void\n }\n // @ts-ignore - step is a prop that is added from the Stepper through React.Children.\n>(({ step, children, className, onStepperItemClick, ...props }, ref) => {\n const {\n activeStep,\n setStep,\n steps,\n orientation,\n labelOrientation,\n scrollTracking,\n variant,\n status,\n isClickable,\n } = useStepper()\n\n const isActive = step === activeStep\n const isCompleted = step < activeStep\n const isDisabled = step > activeStep && !isClickable\n\n const isLastStep = step === steps.length - 1\n\n const isVertical = orientation === \"vertical\"\n const isVerticalLabel = labelOrientation === \"vertical\"\n\n const isError = isActive && status === \"error\"\n\n let icon = steps[step].icon || step + 1\n if (status !== \"default\" && isActive) {\n icon = icons[status!]\n }\n if (isCompleted) {\n icon = icons.success\n }\n\n const content = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type !== StepperFooter\n )\n\n const footer = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type === StepperFooter\n )[0] as React.ReactElement\n\n const onClickItem = (e: React.MouseEvent) => {\n if (isDisabled) {\n return\n }\n if (onStepperItemClick) {\n return onStepperItemClick(e)\n }\n setStep(step)\n }\n\n return (\n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n >\n \n {icon}\n \n \n

\n {steps[step].label}\n

\n {steps[step].description && (\n

\n {steps[step].description}\n

\n )}\n
\n
\n
\n {!isLastStep && (\n \n )}\n {isVertical && isActive && (\n
\n {content}\n {footer}\n
\n )}\n
\n
\n )\n})\n\nStepperItem.displayName = \"StepperItem\"\n\nexport const StepperFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n return (\n
\n {children}\n
\n )\n})\n\nStepperFooter.displayName = \"StepperFooter\"\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/stepper-form.tsx b/apps/www/registry/default/example/stepper-form.tsx new file mode 100644 index 00000000000..0f67b16b1a2 --- /dev/null +++ b/apps/www/registry/default/example/stepper-form.tsx @@ -0,0 +1,190 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" + +import { Button } from "@/registry/default/ui/button" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/registry/default/ui/form" +import { Input } from "@/registry/default/ui/input" +import { + Stepper, + StepperFooter, + StepperItem, + useStepper, +} from "@/registry/default/ui/stepper" +import { toast } from "@/registry/default/ui/use-toast" + +const steps = [ + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, +] + +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + if (index === 0) { + return ( + + + + ) + } + return ( + + + + ) + })} + + + + +
+ ) +} + +const FormSchema = z.object({ + username: z.string().min(2, { + message: "Username must be at least 2 characters.", + }), +}) + +function FirstStepForm() { + const { nextStep } = useStepper() + + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: "", + }, + }) + + function onSubmit(data: z.infer) { + nextStep() + toast({ + title: "First step submitted!", + }) + } + + return ( +
+ + ( + + Username + + + + + This is your public display name. + + + + )} + /> + + + + ) +} + +function SecondStepForm() { + const { nextStep } = useStepper() + + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: "", + }, + }) + + function onSubmit(data: z.infer) { + nextStep() + toast({ + title: "Second step submitted!", + }) + } + + return ( +
+ + ( + + Username + + + + + This is your public display name. + + + + )} + /> + + + + ) +} + +function StepperFormActions() { + const { + activeStep, + isLastStep, + isOptionalStep, + isDisabledStep, + prevStep, + resetSteps, + steps, + } = useStepper() + + return ( +
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )} +
+ ) +} + +function MyStepperFooter() { + const { activeStep, resetSteps, steps } = useStepper() + + if (activeStep !== steps.length) { + return null + } + + return ( +
+ +
+ ) +} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index a686b77708c..d7c4c1fe805 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -128,11 +128,6 @@ export const Stepper = React.forwardRef< if (!React.isValidElement(child)) { throw new Error("Stepper children must be valid React elements.") } - if (child.type !== StepperItem && child.type !== StepperFooter) { - throw new Error( - "Stepper children must be either or ." - ) - } if (child.type === StepperFooter) { footer.push(child) return null diff --git a/apps/www/registry/new-york/example/stepper-form.tsx b/apps/www/registry/new-york/example/stepper-form.tsx new file mode 100644 index 00000000000..b83b1d62b43 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-form.tsx @@ -0,0 +1,190 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import * as z from "zod" + +import { Button } from "@/registry/new-york/ui/button" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/registry/new-york/ui/form" +import { Input } from "@/registry/new-york/ui/input" +import { + Stepper, + StepperFooter, + StepperItem, + useStepper, +} from "@/registry/new-york/ui/stepper" +import { toast } from "@/registry/new-york/ui/use-toast" + +const steps = [ + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, +] + +export default function StepperDemo() { + return ( +
+ + {steps.map((step, index) => { + if (index === 0) { + return ( + + + + ) + } + return ( + + + + ) + })} + + + + +
+ ) +} + +const FormSchema = z.object({ + username: z.string().min(2, { + message: "Username must be at least 2 characters.", + }), +}) + +function FirstStepForm() { + const { nextStep } = useStepper() + + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: "", + }, + }) + + function onSubmit(data: z.infer) { + nextStep() + toast({ + title: "First step submitted!", + }) + } + + return ( +
+ + ( + + Username + + + + + This is your public display name. + + + + )} + /> + + + + ) +} + +function SecondStepForm() { + const { nextStep } = useStepper() + + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + username: "", + }, + }) + + function onSubmit(data: z.infer) { + nextStep() + toast({ + title: "Second step submitted!", + }) + } + + return ( +
+ + ( + + Username + + + + + This is your public display name. + + + + )} + /> + + + + ) +} + +function StepperFormActions() { + const { + activeStep, + isLastStep, + isOptionalStep, + isDisabledStep, + prevStep, + resetSteps, + steps, + } = useStepper() + + return ( +
+ {activeStep === steps.length ? ( + <> + + + ) : ( + <> + + + + )} +
+ ) +} + +function MyStepperFooter() { + const { activeStep, resetSteps, steps } = useStepper() + + if (activeStep !== steps.length) { + return null + } + + return ( +
+ +
+ ) +} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index c3b327a664c..baf9b970f2b 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -29,6 +29,7 @@ interface ContextStepperProps extends StepperProps { resetSteps: () => void setStep: (step: number) => void activeStep: number + setStatus: (status: StepperProps["status"]) => void } const StepperContext = React.createContext({ @@ -39,6 +40,7 @@ const StepperContext = React.createContext({ resetSteps: () => {}, setStep: () => {}, activeStep: 0, + setStatus: () => {}, }) const StepperProvider = ({ @@ -49,6 +51,9 @@ const StepperProvider = ({ children: React.ReactNode }) => { const [activeStep, setActiveStep] = React.useState(value.initialStep) + const [status, setStatus] = React.useState( + value.status + ) const nextStep = () => { setActiveStep((prev) => prev + 1) } @@ -74,6 +79,8 @@ const StepperProvider = ({ resetSteps, setStep, activeStep, + status, + setStatus, }} > {children} @@ -128,11 +135,6 @@ export const Stepper = React.forwardRef< if (!React.isValidElement(child)) { throw new Error("Stepper children must be valid React elements.") } - if (child.type !== StepperItem && child.type !== StepperFooter) { - throw new Error( - "Stepper children must be either or ." - ) - } if (child.type === StepperFooter) { footer.push(child) return null diff --git a/apps/www/registry/registry.ts b/apps/www/registry/registry.ts index 42d85315847..09110431a13 100644 --- a/apps/www/registry/registry.ts +++ b/apps/www/registry/registry.ts @@ -221,7 +221,7 @@ const ui: Registry = [ { name: "stepper", type: "components:ui", - files: ["ui/stepper.tsx", "ui/use-stepper.ts"], + files: ["ui/stepper.tsx"], }, { name: "switch", @@ -817,6 +817,12 @@ const example: Registry = [ registryDependencies: ["stepper"], files: ["example/stepper-description.tsx"], }, + { + name: "stepper-form", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-form.tsx"], + }, { name: "stepper-custom-icons", type: "components:example", From 655b44f992a9a16ea249f4ab0d6eae20def509c6 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Tue, 30 Jan 2024 10:22:31 -0300 Subject: [PATCH 17/62] fix: avoid using the index as a key --- .../example/stepper-clickable-steps.tsx | 8 ++-- .../default/example/stepper-custom-icons.tsx | 8 ++-- .../registry/default/example/stepper-demo.tsx | 8 +++- .../default/example/stepper-description.tsx | 8 ++-- .../registry/default/example/stepper-form.tsx | 38 +++++++++++-------- .../example/stepper-label-orientation.tsx | 6 +-- .../example/stepper-optional-steps.tsx | 8 ++-- .../default/example/stepper-orientation.tsx | 8 +++- .../default/example/stepper-status.tsx | 8 +++- apps/www/registry/default/ui/stepper.tsx | 1 + .../example/stepper-clickable-steps.tsx | 8 ++-- .../new-york/example/stepper-custom-icons.tsx | 8 ++-- .../new-york/example/stepper-demo.tsx | 8 +++- .../new-york/example/stepper-description.tsx | 8 ++-- .../new-york/example/stepper-form.tsx | 38 +++++++++++-------- .../example/stepper-label-orientation.tsx | 6 +-- .../example/stepper-optional-steps.tsx | 8 ++-- .../new-york/example/stepper-orientation.tsx | 8 +++- .../new-york/example/stepper-status.tsx | 8 +++- apps/www/registry/new-york/ui/stepper.tsx | 1 + 20 files changed, 120 insertions(+), 82 deletions(-) diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index 5d137a79f3c..23d1084a0b7 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/default/ui/stepper" const steps = [ - { label: "Step 1" }, - { label: "Step 2", optional: true }, - { label: "Step 3" }, + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2", optional: true }, + { id: 2, label: "Step 3" }, ] export default function StepperDemo() { @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( alert(`You clicked on step ${index + 1}!`) } diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx index 45e6562b6de..b09a34cb56d 100644 --- a/apps/www/registry/default/example/stepper-custom-icons.tsx +++ b/apps/www/registry/default/example/stepper-custom-icons.tsx @@ -9,9 +9,9 @@ import { } from "@/registry/default/ui/stepper" const steps = [ - { label: "Step 1", icon: }, - { label: "Step 2", icon: }, - { label: "Step 3", icon: }, + { id: 0, label: "Step 1", icon: }, + { id: 1, label: "Step 2", icon: }, + { id: 2, label: "Step 3", icon: }, ] export default function StepperDemo() { @@ -20,7 +20,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx index 38dd75da331..ae621f3de2a 100644 --- a/apps/www/registry/default/example/stepper-demo.tsx +++ b/apps/www/registry/default/example/stepper-demo.tsx @@ -6,7 +6,11 @@ import { useStepper, } from "@/registry/default/ui/stepper" -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +const steps = [ + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2" }, + { id: 2, label: "Step 3" }, +] export default function StepperDemo() { return ( @@ -14,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/default/example/stepper-description.tsx b/apps/www/registry/default/example/stepper-description.tsx index d6738702eec..c24181c5705 100644 --- a/apps/www/registry/default/example/stepper-description.tsx +++ b/apps/www/registry/default/example/stepper-description.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/default/ui/stepper" const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, - { label: "Step 3", description: "Description 3" }, + { id: 0, label: "Step 1", description: "Description 1" }, + { id: 1, label: "Step 2", description: "Description 2" }, + { id: 2, label: "Step 3", description: "Description 3" }, ] export default function StepperDemo() { @@ -18,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/default/example/stepper-form.tsx b/apps/www/registry/default/example/stepper-form.tsx index 0f67b16b1a2..30ac40190a6 100644 --- a/apps/www/registry/default/example/stepper-form.tsx +++ b/apps/www/registry/default/example/stepper-form.tsx @@ -24,8 +24,8 @@ import { import { toast } from "@/registry/default/ui/use-toast" const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, + { id: 0, label: "Step 1", description: "Description 1" }, + { id: 1, label: "Step 2", description: "Description 2" }, ] export default function StepperDemo() { @@ -35,13 +35,13 @@ export default function StepperDemo() { {steps.map((step, index) => { if (index === 0) { return ( - + ) } return ( - + ) @@ -54,7 +54,7 @@ export default function StepperDemo() { ) } -const FormSchema = z.object({ +const FirstFormSchema = z.object({ username: z.string().min(2, { message: "Username must be at least 2 characters.", }), @@ -63,14 +63,14 @@ const FormSchema = z.object({ function FirstStepForm() { const { nextStep } = useStepper() - const form = useForm>({ - resolver: zodResolver(FormSchema), + const form = useForm>({ + resolver: zodResolver(FirstFormSchema), defaultValues: { username: "", }, }) - function onSubmit(data: z.infer) { + function onSubmit(data: z.infer) { nextStep() toast({ title: "First step submitted!", @@ -102,17 +102,23 @@ function FirstStepForm() { ) } +const SecondFormSchema = z.object({ + password: z.string().min(8, { + message: "Password must be at least 8 characters.", + }), +}) + function SecondStepForm() { const { nextStep } = useStepper() - const form = useForm>({ - resolver: zodResolver(FormSchema), + const form = useForm>({ + resolver: zodResolver(SecondFormSchema), defaultValues: { - username: "", + password: "", }, }) - function onSubmit(data: z.infer) { + function onSubmit(data: z.infer) { nextStep() toast({ title: "Second step submitted!", @@ -124,15 +130,15 @@ function SecondStepForm() {
( - Username + Password - + - This is your public display name. + This is your private password. diff --git a/apps/www/registry/default/example/stepper-label-orientation.tsx b/apps/www/registry/default/example/stepper-label-orientation.tsx index 87e9dbfb36d..2e3c0c39438 100644 --- a/apps/www/registry/default/example/stepper-label-orientation.tsx +++ b/apps/www/registry/default/example/stepper-label-orientation.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/default/ui/stepper" const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, - { label: "Step 3", description: "Description 3" }, + { id: 0, label: "Step 1", description: "Description 1" }, + { id: 1, label: "Step 2", description: "Description 2" }, + { id: 2, label: "Step 3", description: "Description 3" }, ] export default function StepperDemo() { diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx index 4949a67660f..39e5d47ab11 100644 --- a/apps/www/registry/default/example/stepper-optional-steps.tsx +++ b/apps/www/registry/default/example/stepper-optional-steps.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/default/ui/stepper" const steps = [ - { label: "Step 1" }, - { label: "Step 2", optional: true }, - { label: "Step 3" }, + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2", optional: true }, + { id: 2, label: "Step 3" }, ] export default function StepperDemo() { @@ -18,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index 97a4f426fbd..ecc1506aee3 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -6,7 +6,11 @@ import { useStepper, } from "@/registry/default/ui/stepper" -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +const steps = [ + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2" }, + { id: 2, label: "Step 3" }, +] export default function StepperDemo() { return ( @@ -14,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/default/example/stepper-status.tsx b/apps/www/registry/default/example/stepper-status.tsx index 11baacd66e5..f1f43a1f063 100644 --- a/apps/www/registry/default/example/stepper-status.tsx +++ b/apps/www/registry/default/example/stepper-status.tsx @@ -10,7 +10,11 @@ import { useStepper, } from "@/registry/default/ui/stepper" -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +const steps = [ + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2" }, + { id: 2, label: "Step 3" }, +] export default function StepperStates() { const [value, setValue] = useState<"loading" | "error">("loading") @@ -47,7 +51,7 @@ function StepperDemo({ {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index d7c4c1fe805..c12e902b9ad 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -9,6 +9,7 @@ import { Separator } from "./separator" interface StepperProps { steps: { + id: string | number label: string | React.ReactNode description?: string | React.ReactNode icon?: React.ReactNode diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx index 7a0429b5b1f..807b18eebcd 100644 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/new-york/ui/stepper" const steps = [ - { label: "Step 1" }, - { label: "Step 2", optional: true }, - { label: "Step 3" }, + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2", optional: true }, + { id: 2, label: "Step 3" }, ] export default function StepperDemo() { @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( alert(`You clicked on step ${index + 1}!`) } diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx index c6312b0f1d5..9676f7d1e2c 100644 --- a/apps/www/registry/new-york/example/stepper-custom-icons.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-icons.tsx @@ -9,9 +9,9 @@ import { } from "@/registry/new-york/ui/stepper" const steps = [ - { label: "Step 1", icon: }, - { label: "Step 2", icon: }, - { label: "Step 3", icon: }, + { id: 0, label: "Step 1", icon: }, + { id: 1, label: "Step 2", icon: }, + { id: 2, label: "Step 3", icon: }, ] export default function StepperDemo() { @@ -20,7 +20,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index 485a0d7b5b7..79d8982f916 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -6,7 +6,11 @@ import { useStepper, } from "@/registry/new-york/ui/stepper" -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +const steps = [ + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2" }, + { id: 2, label: "Step 3" }, +] export default function StepperDemo() { return ( @@ -14,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/new-york/example/stepper-description.tsx b/apps/www/registry/new-york/example/stepper-description.tsx index 07fafa38e3a..76cc347de25 100644 --- a/apps/www/registry/new-york/example/stepper-description.tsx +++ b/apps/www/registry/new-york/example/stepper-description.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/new-york/ui/stepper" const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, - { label: "Step 3", description: "Description 3" }, + { id: 0, label: "Step 1", description: "Description 1" }, + { id: 1, label: "Step 2", description: "Description 2" }, + { id: 2, label: "Step 3", description: "Description 3" }, ] export default function StepperDemo() { @@ -18,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/new-york/example/stepper-form.tsx b/apps/www/registry/new-york/example/stepper-form.tsx index b83b1d62b43..b752cfd76d1 100644 --- a/apps/www/registry/new-york/example/stepper-form.tsx +++ b/apps/www/registry/new-york/example/stepper-form.tsx @@ -24,8 +24,8 @@ import { import { toast } from "@/registry/new-york/ui/use-toast" const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, + { id: 0, label: "Step 1", description: "Description 1" }, + { id: 1, label: "Step 2", description: "Description 2" }, ] export default function StepperDemo() { @@ -35,13 +35,13 @@ export default function StepperDemo() { {steps.map((step, index) => { if (index === 0) { return ( - + ) } return ( - + ) @@ -54,7 +54,7 @@ export default function StepperDemo() { ) } -const FormSchema = z.object({ +const FirstFormSchema = z.object({ username: z.string().min(2, { message: "Username must be at least 2 characters.", }), @@ -63,14 +63,14 @@ const FormSchema = z.object({ function FirstStepForm() { const { nextStep } = useStepper() - const form = useForm>({ - resolver: zodResolver(FormSchema), + const form = useForm>({ + resolver: zodResolver(FirstFormSchema), defaultValues: { username: "", }, }) - function onSubmit(data: z.infer) { + function onSubmit(data: z.infer) { nextStep() toast({ title: "First step submitted!", @@ -102,17 +102,23 @@ function FirstStepForm() { ) } +const SecondFormSchema = z.object({ + password: z.string().min(8, { + message: "Password must be at least 8 characters.", + }), +}) + function SecondStepForm() { const { nextStep } = useStepper() - const form = useForm>({ - resolver: zodResolver(FormSchema), + const form = useForm>({ + resolver: zodResolver(SecondFormSchema), defaultValues: { - username: "", + password: "", }, }) - function onSubmit(data: z.infer) { + function onSubmit(data: z.infer) { nextStep() toast({ title: "Second step submitted!", @@ -124,15 +130,15 @@ function SecondStepForm() { ( - Username + Password - + - This is your public display name. + This is your private password. diff --git a/apps/www/registry/new-york/example/stepper-label-orientation.tsx b/apps/www/registry/new-york/example/stepper-label-orientation.tsx index 47cb31d07f3..af6facad927 100644 --- a/apps/www/registry/new-york/example/stepper-label-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-label-orientation.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/new-york/ui/stepper" const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, - { label: "Step 3", description: "Description 3" }, + { id: 0, label: "Step 1", description: "Description 1" }, + { id: 1, label: "Step 2", description: "Description 2" }, + { id: 2, label: "Step 3", description: "Description 3" }, ] export default function StepperDemo() { diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx index 4b1a4a46318..5126c15d8a7 100644 --- a/apps/www/registry/new-york/example/stepper-optional-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-optional-steps.tsx @@ -7,9 +7,9 @@ import { } from "@/registry/new-york/ui/stepper" const steps = [ - { label: "Step 1" }, - { label: "Step 2", optional: true }, - { label: "Step 3" }, + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2", optional: true }, + { id: 2, label: "Step 3" }, ] export default function StepperDemo() { @@ -18,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index 6d02189f840..be6c1a2a686 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -6,7 +6,11 @@ import { useStepper, } from "@/registry/new-york/ui/stepper" -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +const steps = [ + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2" }, + { id: 2, label: "Step 3" }, +] export default function StepperDemo() { return ( @@ -14,7 +18,7 @@ export default function StepperDemo() { {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/new-york/example/stepper-status.tsx b/apps/www/registry/new-york/example/stepper-status.tsx index 914b7f54b80..93710f0293a 100644 --- a/apps/www/registry/new-york/example/stepper-status.tsx +++ b/apps/www/registry/new-york/example/stepper-status.tsx @@ -10,7 +10,11 @@ import { useStepper, } from "@/registry/new-york/ui/stepper" -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +const steps = [ + { id: 0, label: "Step 1" }, + { id: 1, label: "Step 2" }, + { id: 2, label: "Step 3" }, +] export default function StepperStates() { const [value, setValue] = useState<"loading" | "error">("loading") @@ -47,7 +51,7 @@ function StepperDemo({ {steps.map((step, index) => { return ( - +

Step {index + 1} content

diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index baf9b970f2b..3fa2acf6812 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -9,6 +9,7 @@ import { Separator } from "./separator" interface StepperProps { steps: { + id: string | number label: string | React.ReactNode description?: string | React.ReactNode icon?: React.ReactNode From ae61132af4e359931481ff9216283f6d8b4e4287 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 22 Mar 2024 16:13:54 -0300 Subject: [PATCH 18/62] feat: new logic, animations and variants --- apps/www/__registry__/index.tsx | 46 +- apps/www/content/docs/components/stepper.mdx | 286 +++-- apps/www/public/registry/index.json | 3 - .../registry/styles/default/carousel.json | 3 - .../registry/styles/default/stepper.json | 2 +- .../registry/styles/new-york/carousel.json | 3 - .../registry/styles/new-york/stepper.json | 2 +- .../example/stepper-clickable-steps.tsx | 93 +- .../default/example/stepper-custom-icons.tsx | 84 +- .../registry/default/example/stepper-demo.tsx | 82 +- .../default/example/stepper-description.tsx | 82 +- .../default/example/stepper-footer-inside.tsx | 74 ++ .../registry/default/example/stepper-form.tsx | 68 +- .../example/stepper-label-orientation.tsx | 66 - .../example/stepper-optional-steps.tsx | 82 +- .../default/example/stepper-orientation.tsx | 84 +- .../default/example/stepper-state.tsx | 75 ++ .../default/example/stepper-status.tsx | 99 -- .../default/example/stepper-variants.tsx | 100 ++ apps/www/registry/default/ui/stepper.tsx | 1096 ++++++++++++---- .../example/stepper-clickable-steps.tsx | 93 +- .../new-york/example/stepper-custom-icons.tsx | 84 +- .../new-york/example/stepper-demo.tsx | 82 +- .../new-york/example/stepper-description.tsx | 82 +- .../example/stepper-footer-inside.tsx | 74 ++ .../new-york/example/stepper-form.tsx | 68 +- .../example/stepper-label-orientation.tsx | 66 - .../example/stepper-optional-steps.tsx | 82 +- .../new-york/example/stepper-orientation.tsx | 84 +- .../new-york/example/stepper-state.tsx | 75 ++ .../new-york/example/stepper-status.tsx | 99 -- .../new-york/example/stepper-variants.tsx | 100 ++ apps/www/registry/new-york/ui/stepper.tsx | 1103 +++++++++++++---- apps/www/registry/registry.ts | 14 +- tailwind.config.cjs | 10 + 35 files changed, 3016 insertions(+), 1530 deletions(-) create mode 100644 apps/www/registry/default/example/stepper-footer-inside.tsx delete mode 100644 apps/www/registry/default/example/stepper-label-orientation.tsx create mode 100644 apps/www/registry/default/example/stepper-state.tsx delete mode 100644 apps/www/registry/default/example/stepper-status.tsx create mode 100644 apps/www/registry/default/example/stepper-variants.tsx create mode 100644 apps/www/registry/new-york/example/stepper-footer-inside.tsx delete mode 100644 apps/www/registry/new-york/example/stepper-label-orientation.tsx create mode 100644 apps/www/registry/new-york/example/stepper-state.tsx delete mode 100644 apps/www/registry/new-york/example/stepper-status.tsx create mode 100644 apps/www/registry/new-york/example/stepper-variants.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 9ed13cf4ba5..6c6a29abeb1 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -1048,6 +1048,13 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/example/stepper-form")), files: ["registry/default/example/stepper-form.tsx"], }, + "stepper-variants": { + name: "stepper-variants", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-variants")), + files: ["registry/default/example/stepper-variants.tsx"], + }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", @@ -1055,12 +1062,12 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/example/stepper-custom-icons")), files: ["registry/default/example/stepper-custom-icons.tsx"], }, - "stepper-label-orientation": { - name: "stepper-label-orientation", + "stepper-footer-inside": { + name: "stepper-footer-inside", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-label-orientation")), - files: ["registry/default/example/stepper-label-orientation.tsx"], + component: React.lazy(() => import("@/registry/default/example/stepper-footer-inside")), + files: ["registry/default/example/stepper-footer-inside.tsx"], }, "stepper-clickable-steps": { name: "stepper-clickable-steps", @@ -1076,12 +1083,12 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/default/example/stepper-optional-steps")), files: ["registry/default/example/stepper-optional-steps.tsx"], }, - "stepper-status": { - name: "stepper-status", + "stepper-state": { + name: "stepper-state", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/default/example/stepper-status")), - files: ["registry/default/example/stepper-status.tsx"], + component: React.lazy(() => import("@/registry/default/example/stepper-state")), + files: ["registry/default/example/stepper-state.tsx"], }, "switch-demo": { name: "switch-demo", @@ -2435,6 +2442,13 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/example/stepper-form")), files: ["registry/new-york/example/stepper-form.tsx"], }, + "stepper-variants": { + name: "stepper-variants", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-variants")), + files: ["registry/new-york/example/stepper-variants.tsx"], + }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", @@ -2442,12 +2456,12 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-icons")), files: ["registry/new-york/example/stepper-custom-icons.tsx"], }, - "stepper-label-orientation": { - name: "stepper-label-orientation", + "stepper-footer-inside": { + name: "stepper-footer-inside", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-label-orientation")), - files: ["registry/new-york/example/stepper-label-orientation.tsx"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-footer-inside")), + files: ["registry/new-york/example/stepper-footer-inside.tsx"], }, "stepper-clickable-steps": { name: "stepper-clickable-steps", @@ -2463,12 +2477,12 @@ export const Index: Record = { component: React.lazy(() => import("@/registry/new-york/example/stepper-optional-steps")), files: ["registry/new-york/example/stepper-optional-steps.tsx"], }, - "stepper-status": { - name: "stepper-status", + "stepper-state": { + name: "stepper-state", type: "components:example", registryDependencies: ["stepper"], - component: React.lazy(() => import("@/registry/new-york/example/stepper-status")), - files: ["registry/new-york/example/stepper-status.tsx"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-state")), + files: ["registry/new-york/example/stepper-state.tsx"], }, "switch-demo": { name: "switch-demo", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 4e4b44001b1..4a46542b67b 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -24,6 +24,34 @@ component: true npx shadcn-ui@latest add stepper ``` +Update `tailwind.config.js` + +Add the following animations to your `tailwind.config.js` file: + +```js showLineNumbers title="tailwind.config.js" {6-13,16-17} +/** @type {import('tailwindcss').Config} */ +module.exports = { + theme: { + extend: { + keyframes: { + "collapsible-down": { + from: { height: "0" }, + to: { height: "var(--radix-collapsible-content-height)" }, + }, + "collapsible-up": { + from: { height: "var(--radix-collapsible-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "collapsible-down": "collapsible-down 0.2s ease-out", + "collapsible-up": "collapsible-up 0.2s ease-out", + }, + }, + }, +} +``` + @@ -48,119 +76,82 @@ npx shadcn-ui@latest add stepper ```tsx import { + Step, Stepper, - StepperItem, - StepperFooter useStepper, + type StepItem, } from "@/components/ui/stepper" ``` -```tsx {1} -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +```tsx +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- - {steps.map((step, index) => { + + {steps.map(({ label }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } ``` -## API - -### `` - -```tsx -interface StepperProps { - steps: { - label: string | React.ReactNode - description?: string | React.ReactNode - icon?: React.ReactNode - optional?: boolean - }[] - initialStep: number - orientation?: "vertical" | "horizontal" - labelOrientation?: "vertical" | "horizontal" - scrollTracking?: boolean - variant?: "default" | "ghost" | "outline" | "secondary" - status?: "default" | "success" | "error" | "loading" - isClickable?: boolean -} -``` - -### `` - -```tsx -interface StepperItemProps { - onStepperItemClick?: () => void -} -``` - -### `useStepper` - -`useStepper` returns values and functions that allow to render the stepper in a modular way. - -```tsx -function useStepper(): StepperProps & { - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void - setStatus: (status: "default" | "success" | "error" | "loading") => void - activeStep: number - isDisabledStep: boolean - isLastStep: boolean - isOptionalStep: boolean - isFinished: boolean -} -``` - ## Examples ### Default @@ -179,91 +170,60 @@ We can add a description to the array of steps -### Label orientation - -If you would like the labels to be positioned below the step icons you can do so using the `labelOrientation` prop on the Stepper component. - - - ### Optional steps If you want to make a step optional, you can add `optional: true` in the array of steps. +In this example, the second step is optional. You can visualize the change of the label in the `Next` button + -### Status +### Variants -Sometimes it is useful to show visual feedback to the user depending on some asynchronous logic. In this case we can use the `status` prop on the Stepper component to show a loading indicator, a success indicator or an error indicator. +There are 3 design variants for the Stepper component: - +- `circles`: allows you to display each step in a circular shape. The label and description are positioned horizontally next to the button of each step. +- `circles-alt`: same circular design as the circles variant but the label and description are positioned vertically below the button of each step. +- `simple`: this variant features a line layout with the title and description positioned below the line. -### Custom Icons + -If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component. +### Responsive - +By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the `responsiveBreakpoint` prop. -### Custom onClick item event +### State -If you need to control the onClick event when a user clicks on a step, you can set the `onStepperItemClick` prop. This event overrides the default setStep, so you must customize it with `useStepper`. +Sometimes it is useful to display visual feedback to the user depending on some asynchronous logic. In this case we can use the `state` prop to display a loading or error indicator with the values of `loading | error`. - +This prop can be used globally within the Stepper component or locally in the Step component affected by this state. -### Responsive + -If you want the stepper to be responsive you can make it responsive by using some logic of your own so that the `orientation` prop changes according to the screen size. +### Custom Icons -For example, you can use a hook like `useMediaQuery` that tells you when the screen becomes a certain size in this way: +If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component. -```tsx {8,10} -import { useMediaQuery } from "@/hooks/use-media-query" -import { - Stepper, - ... -} from "@/components/ui/stepper" + -export default function Page() { - const isMobile = useMediaQuery("(max-width: 640px)") - return ( - - {/* ... */} - - ) -} -``` + + To change the general check and error icons, we can use the `checkIcon` and + `errorIcon` prop inside the Stepper component + -### Footer inside the step +### Clickable steps -When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To do this, we can change the footer structure and place it below the first internal children of `StepperItem`. +If you need the step buttons to be clickable and to be able to set a custom logic for the onClick event, we can use the `onClickStep` prop in the Stepper component globally or in Step locally. -```tsx -const steps = [{ label: "Step 1" }, { label: "Step 2" }, { label: "Step 3" }] +In this example we have placed a global alert when any step is clicked. Try clicking on any step to see the result. -export default function StepperDemo() { - return ( -
- - {steps.map((step, index) => { - return ( - -
-

Step {index + 1} content

-
- - - -
- ) - })} -
-
- ) -} + -function MyStepperFooter() { - ... -} -``` +### Footer inside the step + +When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To achieve this, we can simply move our footer below all the steps inside the Stepper component + + ### Scroll tracking @@ -278,3 +238,37 @@ This example uses the `Form` component of shadcn and the `react-hook-form` hooks You can also use the component with server actions. + +### Custom styles + +The Stepper component allows you to customize all the styles of each section that composes the component in terms of tailwind classes or custom classes that you want to define in CSS. + +Each name belongs to the general classes defined in each internal section of the component that are prefixed with `stepper__` followed by the class name. + +The following is the list of all the classes that can be customized through the `styles` prop: + +```ts +styles?: { + /** Styles for the main container */ + "main-container"?: string + /** Styles for the horizontal step */ + "horizontal-step"?: string + /** Styles for the horizontal step container (button and labels) */ + "horizontal-step-container"?: string + /** Styles for the vertical step */ + "vertical-step"?: string + /** Styles for the vertical step container (button and labels) */ + "vertical-step-container"?: string + /** Styles for the vertical step content */ + "vertical-step-content"?: string + /** Styles for the step button container */ + "step-button-container"?: string + /** Styles for the label and description container */ + "step-label-container"?: string + /** Styles for the step label */ + "step-label"?: string + /** Styles for the step description */ + "step-description"?: string +} +``` + diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index a96fa35c0e1..9e93e297fb5 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -102,9 +102,6 @@ "dependencies": [ "embla-carousel-react" ], - "devDependencies": [ - "embla-carousel" - ], "registryDependencies": [ "button" ], diff --git a/apps/www/public/registry/styles/default/carousel.json b/apps/www/public/registry/styles/default/carousel.json index dbdecbec95f..d7e14e3382a 100644 --- a/apps/www/public/registry/styles/default/carousel.json +++ b/apps/www/public/registry/styles/default/carousel.json @@ -3,9 +3,6 @@ "dependencies": [ "embla-carousel-react" ], - "devDependencies": [ - "embla-carousel" - ], "registryDependencies": [ "button" ], diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index 09c83d23444..2738a058f75 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "import React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\n\ninterface StepperProps {\n steps: {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n icon?: React.ReactNode\n optional?: boolean\n }[]\n initialStep: number\n orientation?: \"vertical\" | \"horizontal\"\n labelOrientation?: \"vertical\" | \"horizontal\"\n scrollTracking?: boolean\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n status?: \"default\" | \"success\" | \"error\" | \"loading\"\n isClickable?: boolean\n}\n\ninterface ContextStepperProps extends StepperProps {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n}\n\nconst StepperContext = React.createContext({\n steps: [],\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n activeStep: 0,\n})\n\nconst StepperProvider = ({\n value,\n children,\n}: {\n value: StepperProps\n children: React.ReactNode\n}) => {\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\nexport function useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const isDisabledStep = context.activeStep === 0\n const isLastStep = context.activeStep === context.steps.length - 1\n const isOptionalStep = context.steps[context.activeStep]?.optional\n const isFinished = context.activeStep === context.steps.length\n\n return {\n ...context,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n isFinished,\n }\n}\n\nexport const Stepper = React.forwardRef<\n HTMLDivElement,\n StepperProps & React.HTMLAttributes\n>(\n (\n {\n initialStep,\n steps,\n status = \"default\",\n orientation = \"horizontal\",\n labelOrientation = \"horizontal\",\n scrollTracking = false,\n children,\n variant = \"default\",\n isClickable = true,\n className,\n ...props\n },\n ref\n ) => {\n const footer = [] as React.ReactElement[]\n\n const items = React.Children.toArray(children).map((child, index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === StepperFooter) {\n footer.push(child)\n return null\n }\n const stepperItemProps = {\n ...child.props,\n step: index,\n }\n\n return React.cloneElement(child, stepperItemProps)\n })\n\n return (\n \n
\n \n {items}\n
\n {orientation === \"horizontal\" && (\n {children}\n )}\n {footer}\n
\n \n )\n }\n)\n\nconst HorizontalContent = ({ children }: { children?: React.ReactNode }) => {\n const { activeStep, isFinished } = useStepper()\n\n if (isFinished) {\n return null\n }\n\n const activeStepperItem = React.Children.toArray(children)[\n activeStep\n ] as React.ReactElement\n\n const content = activeStepperItem?.props?.children\n\n return content\n}\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nconst icons = {\n success: ,\n error: ,\n loading: ,\n default: null,\n} as const\n\nexport const StepperItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes & {\n onStepperItemClick?: (\n e: React.MouseEvent\n ) => void\n }\n // @ts-ignore - step is a prop that is added from the Stepper through React.Children.\n>(({ step, children, className, onStepperItemClick, ...props }, ref) => {\n const {\n activeStep,\n setStep,\n steps,\n orientation,\n labelOrientation,\n scrollTracking,\n variant,\n status,\n isClickable,\n } = useStepper()\n\n const isActive = step === activeStep\n const isCompleted = step < activeStep\n const isDisabled = step > activeStep && !isClickable\n\n const isLastStep = step === steps.length - 1\n\n const isVertical = orientation === \"vertical\"\n const isVerticalLabel = labelOrientation === \"vertical\"\n\n const isError = isActive && status === \"error\"\n\n let icon = steps[step].icon || step + 1\n if (status !== \"default\" && isActive) {\n icon = icons[status!]\n }\n if (isCompleted) {\n icon = icons.success\n }\n\n const content = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type !== StepperFooter\n )\n\n const footer = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type === StepperFooter\n )[0] as React.ReactElement\n\n const onClickItem = (e: React.MouseEvent) => {\n if (isDisabled) {\n return\n }\n if (onStepperItemClick) {\n return onStepperItemClick(e)\n }\n setStep(step)\n }\n\n return (\n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n >\n \n {icon}\n \n \n

\n {steps[step].label}\n

\n {steps[step].description && (\n

\n {steps[step].description}\n

\n )}\n
\n
\n
\n {!isLastStep && (\n \n )}\n {isVertical && isActive && (\n
\n {content}\n {footer}\n
\n )}\n
\n
\n )\n})\n\nStepperItem.displayName = \"StepperItem\"\n\nexport const StepperFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n return (\n
\n {children}\n
\n )\n})\n\nStepperFooter.displayName = \"StepperFooter\"\n" + "content": "import * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n reset: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n reset: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const reset = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptional = currentStep?.optional\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptional,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n responsiveBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"2rem\",\n md: \"2.25rem\",\n lg: \"2.5rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n responsiveBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${responsiveBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/carousel.json b/apps/www/public/registry/styles/new-york/carousel.json index c1733592006..5de0a013f37 100644 --- a/apps/www/public/registry/styles/new-york/carousel.json +++ b/apps/www/public/registry/styles/new-york/carousel.json @@ -3,9 +3,6 @@ "dependencies": [ "embla-carousel-react" ], - "devDependencies": [ - "embla-carousel" - ], "registryDependencies": [ "button" ], diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index 68e17eafae4..c033347785d 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "import React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { Check, Loader2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Separator } from \"./separator\"\n\ninterface StepperProps {\n steps: {\n label: string | React.ReactNode\n description?: string | React.ReactNode\n icon?: React.ReactNode\n optional?: boolean\n }[]\n initialStep: number\n orientation?: \"vertical\" | \"horizontal\"\n labelOrientation?: \"vertical\" | \"horizontal\"\n scrollTracking?: boolean\n variant?: \"default\" | \"ghost\" | \"outline\" | \"secondary\"\n status?: \"default\" | \"success\" | \"error\" | \"loading\"\n isClickable?: boolean\n}\n\ninterface ContextStepperProps extends StepperProps {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n activeStep: number\n setStatus: (status: StepperProps[\"status\"]) => void\n}\n\nconst StepperContext = React.createContext({\n steps: [],\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n activeStep: 0,\n setStatus: () => {},\n})\n\nconst StepperProvider = ({\n value,\n children,\n}: {\n value: StepperProps\n children: React.ReactNode\n}) => {\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n const [status, setStatus] = React.useState(\n value.status\n )\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\nexport function useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const isDisabledStep = context.activeStep === 0\n const isLastStep = context.activeStep === context.steps.length - 1\n const isOptionalStep = context.steps[context.activeStep]?.optional\n const isFinished = context.activeStep === context.steps.length\n\n return {\n ...context,\n isDisabledStep,\n isLastStep,\n isOptionalStep,\n isFinished,\n }\n}\n\nexport const Stepper = React.forwardRef<\n HTMLDivElement,\n StepperProps & React.HTMLAttributes\n>(\n (\n {\n initialStep,\n steps,\n status = \"default\",\n orientation = \"horizontal\",\n labelOrientation = \"horizontal\",\n scrollTracking = false,\n children,\n variant = \"default\",\n isClickable = true,\n className,\n ...props\n },\n ref\n ) => {\n const footer = [] as React.ReactElement[]\n\n const items = React.Children.toArray(children).map((child, index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === StepperFooter) {\n footer.push(child)\n return null\n }\n const stepperItemProps = {\n ...child.props,\n step: index,\n }\n\n return React.cloneElement(child, stepperItemProps)\n })\n\n return (\n \n
\n \n {items}\n
\n {orientation === \"horizontal\" && (\n {children}\n )}\n {footer}\n
\n \n )\n }\n)\n\nconst HorizontalContent = ({ children }: { children?: React.ReactNode }) => {\n const { activeStep, isFinished } = useStepper()\n\n if (isFinished) {\n return null\n }\n\n const activeStepperItem = React.Children.toArray(children)[\n activeStep\n ] as React.ReactElement\n\n const content = activeStepperItem?.props?.children\n\n return content\n}\n\nconst stepperItemVariants = cva(\"relative flex flex-row gap-2\", {\n variants: {\n isLastStep: {\n true: \"flex-[0_0_auto] justify-end\",\n false: \"flex-[1_0_auto] justify-start\",\n },\n isVertical: {\n true: \"flex-col\",\n false: \"items-center\",\n },\n },\n compoundVariants: [\n {\n isVertical: true,\n isLastStep: true,\n class: \"w-full flex-[1_0_auto] flex-col items-start justify-start\",\n },\n ],\n})\n\nconst icons = {\n success: ,\n error: ,\n loading: ,\n default: null,\n} as const\n\nexport const StepperItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes & {\n onStepperItemClick?: (\n e: React.MouseEvent\n ) => void\n }\n // @ts-ignore - step is a prop that is added from the Stepper through React.Children.\n>(({ step, children, className, onStepperItemClick, ...props }, ref) => {\n const {\n activeStep,\n setStep,\n steps,\n orientation,\n labelOrientation,\n scrollTracking,\n variant,\n status,\n isClickable,\n } = useStepper()\n\n const isActive = step === activeStep\n const isCompleted = step < activeStep\n const isDisabled = step > activeStep && !isClickable\n\n const isLastStep = step === steps.length - 1\n\n const isVertical = orientation === \"vertical\"\n const isVerticalLabel = labelOrientation === \"vertical\"\n\n const isError = isActive && status === \"error\"\n\n let icon = steps[step].icon || step + 1\n if (status !== \"default\" && isActive) {\n icon = icons[status!]\n }\n if (isCompleted) {\n icon = icons.success\n }\n\n const content = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type !== StepperFooter\n )\n\n const footer = React.Children.toArray(children).filter(\n (child) => React.isValidElement(child) && child.type === StepperFooter\n )[0] as React.ReactElement\n\n const onClickItem = (e: React.MouseEvent) => {\n if (isDisabled) {\n return\n }\n if (onStepperItemClick) {\n return onStepperItemClick(e)\n }\n setStep(step)\n }\n\n return (\n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n >\n \n {icon}\n \n \n

\n {steps[step].label}\n

\n {steps[step].description && (\n

\n {steps[step].description}\n

\n )}\n \n \n
\n {!isLastStep && (\n \n )}\n {isVertical && isActive && (\n
\n {content}\n {footer}\n
\n )}\n
\n \n )\n})\n\nStepperItem.displayName = \"StepperItem\"\n\nexport const StepperFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ children, ...props }, ref) => {\n return (\n
\n {children}\n
\n )\n})\n\nStepperFooter.displayName = \"StepperFooter\"\n" + "content": "import * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n reset: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n reset: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const reset = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptional = currentStep?.optional\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptional,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n responsiveBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"1.75rem\",\n md: \"2rem\",\n lg: \"2.25rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n responsiveBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${responsiveBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index 23d1084a0b7..d77997ba299 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -1,71 +1,78 @@ import { Button } from "@/registry/default/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/default/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2", optional: true }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- - {steps.map((step, index) => { + alert("You clicked on a step!")} + > + {steps.map(({ label }, index) => { return ( - - alert(`You clicked on step ${index + 1}!`) - } - > -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx index b09a34cb56d..6ddf8af8716 100644 --- a/apps/www/registry/default/example/stepper-custom-icons.tsx +++ b/apps/www/registry/default/example/stepper-custom-icons.tsx @@ -1,68 +1,76 @@ -import { Calendar, Lock, User } from "lucide-react" +import { Building, Star, User } from "lucide-react" import { Button } from "@/registry/default/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/default/ui/stepper" const steps = [ - { id: 0, label: "Step 1", icon: }, - { id: 1, label: "Step 2", icon: }, - { id: 2, label: "Step 3", icon: }, -] + { label: "Step 1", icon: User }, + { label: "Step 2", icon: Building }, + { label: "Step 3", icon: Star }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label, icon }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx index ae621f3de2a..385015fa587 100644 --- a/apps/www/registry/default/example/stepper-demo.tsx +++ b/apps/www/registry/default/example/stepper-demo.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/default/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/default/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2" }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/default/example/stepper-description.tsx b/apps/www/registry/default/example/stepper-description.tsx index c24181c5705..af57848d2a7 100644 --- a/apps/www/registry/default/example/stepper-description.tsx +++ b/apps/www/registry/default/example/stepper-description.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/default/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/default/ui/stepper" const steps = [ - { id: 0, label: "Step 1", description: "Description 1" }, - { id: 1, label: "Step 2", description: "Description 2" }, - { id: 2, label: "Step 3", description: "Description 3" }, -] + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, + { label: "Step 3", description: "Description 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label, description }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/default/example/stepper-footer-inside.tsx b/apps/www/registry/default/example/stepper-footer-inside.tsx new file mode 100644 index 00000000000..446f8ae25b1 --- /dev/null +++ b/apps/www/registry/default/example/stepper-footer-inside.tsx @@ -0,0 +1,74 @@ +import { Button } from "@/registry/new-york/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/new-york/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ) + })} + +
+
+ ) +} + +const StepButtons = () => { + const { nextStep, prevStep, activeStep, isLastStep, isOptional } = + useStepper() + return ( +
+ + +
+ ) +} + +const FinalStep = () => { + const { hasCompletedAllSteps, reset } = useStepper() + + if (!hasCompletedAllSteps) { + return null + } + + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ) +} diff --git a/apps/www/registry/default/example/stepper-form.tsx b/apps/www/registry/default/example/stepper-form.tsx index 30ac40190a6..871f4de2337 100644 --- a/apps/www/registry/default/example/stepper-form.tsx +++ b/apps/www/registry/default/example/stepper-form.tsx @@ -15,12 +15,7 @@ import { FormMessage, } from "@/registry/default/ui/form" import { Input } from "@/registry/default/ui/input" -import { - Stepper, - StepperFooter, - StepperItem, - useStepper, -} from "@/registry/default/ui/stepper" +import { Step, Stepper, useStepper } from "@/registry/default/ui/stepper" import { toast } from "@/registry/default/ui/use-toast" const steps = [ @@ -31,24 +26,30 @@ const steps = [ export default function StepperDemo() { return (
- + {steps.map((step, index) => { if (index === 0) { return ( - + - + ) } return ( - + - + ) })} - - - +
) @@ -137,9 +138,7 @@ function SecondStepForm() { - - This is your private password. - + This is your private password. )} @@ -152,28 +151,33 @@ function SecondStepForm() { function StepperFormActions() { const { + nextStep, + prevStep, + reset, activeStep, + hasCompletedAllSteps, isLastStep, - isOptionalStep, - isDisabledStep, - prevStep, - resetSteps, - steps, + isOptional, } = useStepper() return ( -
- {activeStep === steps.length ? ( - <> - - +
+ {hasCompletedAllSteps ? ( + ) : ( <> - - )} @@ -182,7 +186,7 @@ function StepperFormActions() { } function MyStepperFooter() { - const { activeStep, resetSteps, steps } = useStepper() + const { activeStep, reset, steps } = useStepper() if (activeStep !== steps.length) { return null @@ -190,7 +194,7 @@ function MyStepperFooter() { return (
- +
) } diff --git a/apps/www/registry/default/example/stepper-label-orientation.tsx b/apps/www/registry/default/example/stepper-label-orientation.tsx deleted file mode 100644 index 2e3c0c39438..00000000000 --- a/apps/www/registry/default/example/stepper-label-orientation.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Stepper, - StepperFooter, - StepperItem, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { id: 0, label: "Step 1", description: "Description 1" }, - { id: 1, label: "Step 2", description: "Description 2" }, - { id: 2, label: "Step 3", description: "Description 3" }, -] - -export default function StepperDemo() { - return ( -
- - {steps.map((step, index) => { - return ( - -
-

Step {index + 1} content

-
-
- ) - })} - - - -
-
- ) -} - -function MyStepperFooter() { - const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, - nextStep, - prevStep, - resetSteps, - steps, - } = useStepper() - - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - - )} -
- ) -} diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx index 39e5d47ab11..36e376940dd 100644 --- a/apps/www/registry/default/example/stepper-optional-steps.tsx +++ b/apps/www/registry/default/example/stepper-optional-steps.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/default/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/default/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2", optional: true }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2", optional: true }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index ecc1506aee3..715a48e52f1 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/default/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/default/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2" }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- - {steps.map((step, index) => { + + {steps.map(({ label }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/default/example/stepper-state.tsx b/apps/www/registry/default/example/stepper-state.tsx new file mode 100644 index 00000000000..35b4ebfdef0 --- /dev/null +++ b/apps/www/registry/default/example/stepper-state.tsx @@ -0,0 +1,75 @@ +import { Button } from "@/registry/default/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/default/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, + isError, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/default/example/stepper-status.tsx b/apps/www/registry/default/example/stepper-status.tsx deleted file mode 100644 index f1f43a1f063..00000000000 --- a/apps/www/registry/default/example/stepper-status.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useState } from "react" - -import { Button } from "@/registry/default/ui/button" -import { Label } from "@/registry/default/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" -import { - Stepper, - StepperFooter, - StepperItem, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2" }, - { id: 2, label: "Step 3" }, -] - -export default function StepperStates() { - const [value, setValue] = useState<"loading" | "error">("loading") - - return ( -
- setValue(value as "loading" | "error")} - className="mb-4" - > -
- - -
-
- - -
-
- -
- ) -} - -function StepperDemo({ - status = "default", -}: { - status?: "default" | "loading" | "error" -}) { - return ( -
- - {steps.map((step, index) => { - return ( - -
-

Step {index + 1} content

-
-
- ) - })} - - - -
-
- ) -} - -function MyStepperFooter() { - const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, - nextStep, - prevStep, - resetSteps, - steps, - } = useStepper() - - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - - )} -
- ) -} diff --git a/apps/www/registry/default/example/stepper-variants.tsx b/apps/www/registry/default/example/stepper-variants.tsx new file mode 100644 index 00000000000..19137db8d1d --- /dev/null +++ b/apps/www/registry/default/example/stepper-variants.tsx @@ -0,0 +1,100 @@ +import * as React from "react" + +import { Button } from "@/registry/default/ui/button" +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" +import { + Step, + StepItem, + Stepper, + useStepper, + type StepperProps, +} from "@/registry/default/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + const [variant, setVariant] = + React.useState("circles") + + return ( +
+ setVariant(value as StepperProps["variant"])} + > +
+ + +
+
+ + +
+
+ + +
+
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index c12e902b9ad..0cdc021626c 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -1,55 +1,55 @@ -import React from "react" +import * as React from "react" import { cva } from "class-variance-authority" -import { Check, Loader2, X } from "lucide-react" +import { CheckIcon, Loader2, LucideIcon, X } from "lucide-react" import { cn } from "@/lib/utils" - -import { Button } from "./button" -import { Separator } from "./separator" - -interface StepperProps { - steps: { - id: string | number - label: string | React.ReactNode - description?: string | React.ReactNode - icon?: React.ReactNode - optional?: boolean - }[] - initialStep: number - orientation?: "vertical" | "horizontal" - labelOrientation?: "vertical" | "horizontal" - scrollTracking?: boolean - variant?: "default" | "ghost" | "outline" | "secondary" - status?: "default" | "success" | "error" | "loading" - isClickable?: boolean -} - -interface ContextStepperProps extends StepperProps { - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void +import { Button } from "@/registry/default/ui/button" +import { + Collapsible, + CollapsibleContent, +} from "@/registry/default/ui/collapsible" + +// <---------- CONTEXT ----------> + +interface StepperContextValue extends StepperProps { + clickable?: boolean + isError?: boolean + isLoading?: boolean + isVertical?: boolean + stepCount?: number + expandVerticalSteps?: boolean activeStep: number + initialStep: number } -const StepperContext = React.createContext({ +const StepperContext = React.createContext< + StepperContextValue & { + nextStep: () => void + prevStep: () => void + reset: () => void + setStep: (step: number) => void + } +>({ steps: [], + activeStep: 0, initialStep: 0, nextStep: () => {}, prevStep: () => {}, - resetSteps: () => {}, + reset: () => {}, setStep: () => {}, - activeStep: 0, }) -const StepperProvider = ({ - value, - children, -}: { - value: StepperProps +type StepperContextProviderProps = { + value: Omit children: React.ReactNode -}) => { +} + +const StepperProvider = ({ value, children }: StepperContextProviderProps) => { + const isError = value.state === "error" + const isLoading = value.state === "loading" + const [activeStep, setActiveStep] = React.useState(value.initialStep) + const nextStep = () => { setActiveStep((prev) => prev + 1) } @@ -58,7 +58,7 @@ const StepperProvider = ({ setActiveStep((prev) => prev - 1) } - const resetSteps = () => { + const reset = () => { setActiveStep(value.initialStep) } @@ -70,11 +70,13 @@ const StepperProvider = ({ {children} @@ -82,289 +84,891 @@ const StepperProvider = ({ ) } -export function useStepper() { +// <---------- HOOKS ----------> + +function useStepper() { const context = React.useContext(StepperContext) if (context === undefined) { throw new Error("useStepper must be used within a StepperProvider") } - const isDisabledStep = context.activeStep === 0 + const { children, className, ...rest } = context + const isLastStep = context.activeStep === context.steps.length - 1 - const isOptionalStep = context.steps[context.activeStep]?.optional - const isFinished = context.activeStep === context.steps.length + const hasCompletedAllSteps = context.activeStep === context.steps.length + + const currentStep = context.steps[context.activeStep] + const isOptional = currentStep?.optional return { - ...context, - isDisabledStep, + ...rest, isLastStep, - isOptionalStep, - isFinished, + hasCompletedAllSteps, + isOptional, + currentStep, + } +} + +function useMediaQuery(query: string) { + const [value, setValue] = React.useState(false) + + React.useEffect(() => { + function onChange(event: MediaQueryListEvent) { + setValue(event.matches) + } + + const result = matchMedia(query) + result.addEventListener("change", onChange) + setValue(result.matches) + + return () => result.removeEventListener("change", onChange) + }, [query]) + + return value +} + +// <---------- STEPS ----------> + +type StepItem = { + label?: string + description?: string + icon?: IconType + optional?: boolean +} + +interface StepOptions { + orientation?: "vertical" | "horizontal" + state?: "loading" | "error" + responsive?: boolean + checkIcon?: IconType + errorIcon?: IconType + onClickStep?: (step: number) => void + responsiveBreakpoint?: string + variant?: "circles" | "circles-alt" | "simple" + expandVerticalSteps?: boolean + size?: "sm" | "md" | "lg" + styles?: { + /** Styles for the main container */ + "main-container"?: string + /** Styles for the horizontal step */ + "horizontal-step"?: string + /** Styles for the horizontal step container (button and labels) */ + "horizontal-step-container"?: string + /** Styles for the vertical step */ + "vertical-step"?: string + /** Styles for the vertical step container (button and labels) */ + "vertical-step-container"?: string + /** Styles for the vertical step content */ + "vertical-step-content"?: string + /** Styles for the step button container */ + "step-button-container"?: string + /** Styles for the label and description container */ + "step-label-container"?: string + /** Styles for the step label */ + "step-label"?: string + /** Styles for the step description */ + "step-description"?: string } + scrollTracking?: boolean +} +interface StepperProps extends StepOptions { + children?: React.ReactNode + className?: string + initialStep: number + steps: StepItem[] +} + +const VARIABLE_SIZES = { + sm: "2rem", + md: "2.25rem", + lg: "2.5rem", } -export const Stepper = React.forwardRef< - HTMLDivElement, - StepperProps & React.HTMLAttributes ->( - ( - { - initialStep, +const Stepper = React.forwardRef( + (props, ref: React.Ref) => { + const { + className, + children, + orientation: orientationProp, + state, + responsive, + checkIcon, + errorIcon, + onClickStep, + responsiveBreakpoint, + expandVerticalSteps = false, + initialStep = 0, + size, steps, - status = "default", - orientation = "horizontal", - labelOrientation = "horizontal", + variant, + styles, scrollTracking = false, - children, - variant = "default", - isClickable = true, - className, - ...props - }, - ref - ) => { - const footer = [] as React.ReactElement[] + ...rest + } = props + + const childArr = React.Children.toArray(children) + + const items = [] as React.ReactElement[] - const items = React.Children.toArray(children).map((child, index) => { + const footer = childArr.map((child, _index) => { if (!React.isValidElement(child)) { throw new Error("Stepper children must be valid React elements.") } - if (child.type === StepperFooter) { - footer.push(child) + if (child.type === Step) { + items.push(child) return null } - const stepperItemProps = { - ...child.props, - step: index, - } - return React.cloneElement(child, stepperItemProps) + return child }) + const stepCount = items.length + + const isMobile = useMediaQuery( + `(max-width: ${responsiveBreakpoint || "768px"})` + ) + + const clickable = !!onClickStep + + const orientation = isMobile && responsive ? "vertical" : orientationProp + + const isVertical = orientation === "vertical" + return ( -
-
- {items} -
- {orientation === "horizontal" && ( - {children} +
+ {items}
+ {orientation === "horizontal" && ( + {items} + )} + {footer} ) } ) -const HorizontalContent = ({ children }: { children?: React.ReactNode }) => { - const { activeStep, isFinished } = useStepper() +Stepper.defaultProps = { + size: "md", + orientation: "horizontal", + responsive: true, +} - if (isFinished) { - return null - } +const VerticalContent = ({ children }: { children: React.ReactNode }) => { + const { activeStep } = useStepper() - const activeStepperItem = React.Children.toArray(children)[ - activeStep - ] as React.ReactElement + const childArr = React.Children.toArray(children) + const stepCount = childArr.length - const content = activeStepperItem?.props?.children + return ( + <> + {React.Children.map(children, (child, i) => { + const isCompletedStep = + (React.isValidElement(child) && + (child.props as any).isCompletedStep) ?? + i < activeStep + const isLastStep = i === stepCount - 1 + const isCurrentStep = i === activeStep + + const stepProps = { + index: i, + isCompletedStep, + isCurrentStep, + isLastStep, + } - return content + if (React.isValidElement(child)) { + return React.cloneElement(child, stepProps) + } + return null + })} + + ) } -const stepperItemVariants = cva("relative flex flex-row gap-2", { - variants: { - isLastStep: { - true: "flex-[0_0_auto] justify-end", - false: "flex-[1_0_auto] justify-start", - }, - isVertical: { - true: "flex-col", - false: "items-center", - }, - }, - compoundVariants: [ - { - isVertical: true, - isLastStep: true, - class: "w-full flex-[1_0_auto] flex-col items-start justify-start", - }, - ], -}) - -const icons = { - success: , - error: , - loading: , - default: null, -} as const - -export const StepperItem = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & { - onStepperItemClick?: ( - e: React.MouseEvent - ) => void +const HorizontalContent = ({ children }: { children: React.ReactNode }) => { + const { activeStep } = useStepper() + const childArr = React.Children.toArray(children) + + const renderHorizontalContent = () => { + if (activeStep <= childArr.length) { + return React.Children.map(childArr[activeStep], (node) => { + if (!React.isValidElement(node)) { + return + } + return React.Children.map(node.props.children, (childNode) => childNode) + }) + } + return null } - // @ts-ignore - step is a prop that is added from the Stepper through React.Children. ->(({ step, children, className, onStepperItemClick, ...props }, ref) => { - const { - activeStep, - setStep, - steps, - orientation, - labelOrientation, - scrollTracking, - variant, - status, - isClickable, - } = useStepper() - const isActive = step === activeStep - const isCompleted = step < activeStep - const isDisabled = step > activeStep && !isClickable + return <>{renderHorizontalContent()} +} - const isLastStep = step === steps.length - 1 +// <---------- STEP ----------> + +interface StepProps extends React.HTMLAttributes { + label?: string | React.ReactNode + description?: string + icon?: IconType + state?: "loading" | "error" + checkIcon?: IconType + errorIcon?: IconType + isCompletedStep?: boolean + isKeepError?: boolean + onClickStep?: (step: number) => void +} - const isVertical = orientation === "vertical" - const isVerticalLabel = labelOrientation === "vertical" +interface StepSharedProps extends StepProps { + isLastStep?: boolean + isCurrentStep?: boolean + index?: number + hasVisited: boolean | undefined + isError?: boolean + isLoading?: boolean +} + +// Props which shouldn't be passed to to the Step component from the user +interface StepInternalConfig { + index: number + isCompletedStep?: boolean + isCurrentStep?: boolean + isLastStep?: boolean +} - const isError = isActive && status === "error" +interface FullStepProps extends StepProps, StepInternalConfig {} - let icon = steps[step].icon || step + 1 - if (status !== "default" && isActive) { - icon = icons[status!] - } - if (isCompleted) { - icon = icons.success +const Step = React.forwardRef( + (props, ref: React.Ref) => { + const { + children, + description, + icon, + state, + checkIcon, + errorIcon, + index, + isCompletedStep, + isCurrentStep, + isLastStep, + isKeepError, + label, + onClickStep, + } = props as FullStepProps + + const { isVertical, isError, isLoading, clickable } = useStepper() + + const hasVisited = isCurrentStep || isCompletedStep + + const sharedProps = { + isLastStep, + isCompletedStep, + isCurrentStep, + index, + isError, + isLoading, + clickable, + label, + description, + hasVisited, + icon, + isKeepError, + checkIcon, + state, + errorIcon, + onClickStep, + } + + const renderStep = () => { + switch (isVertical) { + case true: + return ( + + {children} + + ) + default: + return + } + } + + return renderStep() } +) - const content = React.Children.toArray(children).filter( - (child) => React.isValidElement(child) && child.type !== StepperFooter - ) +// <---------- VERTICAL STEP ----------> - const footer = React.Children.toArray(children).filter( - (child) => React.isValidElement(child) && child.type === StepperFooter - )[0] as React.ReactElement +type VerticalStepProps = StepSharedProps & { + children?: React.ReactNode +} - const onClickItem = (e: React.MouseEvent) => { - if (isDisabled) { - return - } - if (onStepperItemClick) { - return onStepperItemClick(e) - } - setStep(step) +const verticalStepVariants = cva( + "flex flex-col relative transition-all duration-200", + { + variants: { + variant: { + circles: cn( + "pb-[var(--step-gap)] gap-[var(--step-gap)]", + "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", + "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", + "[&:not(:last-child)]:after:absolute", + "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]", + "[&:not(:last-child)]:after:bottom-[var(--step-gap)]", + "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200" + ), + simple: "flex-1 border-t-0 mb-4", + }, + }, } +) - return ( -
+const VerticalStep = React.forwardRef( + (props, ref) => { + const { + children, + index, + isCompletedStep, + isCurrentStep, + label, + description, + icon, + hasVisited, + state, + checkIcon: checkIconProp, + errorIcon: errorIconProp, + onClickStep, + } = props + + const { + checkIcon: checkIconContext, + errorIcon: errorIconContext, + isError, + isLoading, + variant, + onClickStep: onClickStepGeneral, + clickable, + expandVerticalSteps, + styles, + scrollTracking, + orientation, + } = useStepper() + + const opacity = hasVisited ? 1 : 0.8 + const localIsLoading = isLoading || state === "loading" + const localIsError = isError || state === "error" + + const active = + variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + const checkIcon = checkIconProp || checkIconContext + const errorIcon = errorIconProp || errorIconContext + + const renderChildren = () => { + if (!expandVerticalSteps) { + return ( + + + {children} + + + ) + } + return children + } + + return (
{ - if (scrollTracking) { - node?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }} + data-active={active} + data-clickable={clickable || !!onClickStep} + data-invalid={localIsError} + onClick={() => + onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0) + } > - + + + + +
{ + if (scrollTracking) { + node?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + }} className={cn( - "w-max text-start", - isVerticalLabel && !isVertical && "text-center" + "stepper__vertical-step-content", + "min-h-4", + variant !== "simple" && "ps-[--step-icon-size]", + variant === "simple" && orientation === "vertical" && "min-h-0", + styles?.["vertical-step-content"] )} > -

- {steps[step].label} -

- {steps[step].description && ( -

- {steps[step].description} -

- )} + {renderChildren()}
-
- {!isLastStep && ( - - )} - {isVertical && isActive && ( -
- {content} - {footer} -
+ ) + } +) + +// <---------- HORIZONTAL STEP ----------> + +const HorizontalStep = React.forwardRef( + (props, ref) => { + const { + isError, + isLoading, + onClickStep, + variant, + clickable, + checkIcon: checkIconContext, + errorIcon: errorIconContext, + styles, + } = useStepper() + + const { + index, + isCompletedStep, + isCurrentStep, + hasVisited, + icon, + label, + description, + isKeepError, + state, + checkIcon: checkIconProp, + errorIcon: errorIconProp, + } = props + + const localIsLoading = isLoading || state === "loading" + const localIsError = isError || state === "error" + + const opacity = hasVisited ? 1 : 0.8 + + const active = + variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + + const checkIcon = checkIconProp || checkIconContext + const errorIcon = errorIconProp || errorIconContext + + return ( +
onClickStep?.(index || 0)} + ref={ref} + > +
+ + + + +
-
- ) + ) + } +) + +// <---------- STEP BUTTON CONTAINER ----------> + +type StepButtonContainerProps = StepSharedProps & { + children?: React.ReactNode +} + +const stepButtonVariants = cva("", { + variants: { + size: { + sm: "w-9 h-9", + md: "w-10 h-10", + lg: "w-11 h-11", + }, + }, + defaultVariants: { + size: "md", + }, }) -StepperItem.displayName = "StepperItem" +const StepButtonContainer = ({ + isCurrentStep, + isCompletedStep, + children, + isError, + isLoading: isLoadingProp, +}: StepButtonContainerProps) => { + const { + clickable, + isLoading: isLoadingContext, + variant, + styles, + size, + } = useStepper() + + const isLoading = isLoadingProp || isLoadingContext + + if (variant === "simple") { + return null + } -export const StepperFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ children, ...props }, ref) => { return ( -
+
+ ) +} + +// <---------- STEP ICON ----------> + +type IconType = LucideIcon | React.ComponentType | undefined + +const iconVariants = cva("", { + variants: { + size: { + sm: "size-4", + md: "size-4", + lg: "size-5", + }, + }, + defaultVariants: { + size: "md", + }, }) -StepperFooter.displayName = "StepperFooter" +interface StepIconProps { + isCompletedStep?: boolean + isCurrentStep?: boolean + isError?: boolean + isLoading?: boolean + isKeepError?: boolean + icon?: IconType + index?: number + checkIcon?: IconType + errorIcon?: IconType +} + +const StepIcon = React.forwardRef( + (props, ref) => { + const { size } = useStepper() + + const { + isCompletedStep, + isCurrentStep, + isError, + isLoading, + isKeepError, + icon: CustomIcon, + index, + checkIcon: CustomCheckIcon, + errorIcon: CustomErrorIcon, + } = props + + const Icon = React.useMemo( + () => (CustomIcon ? CustomIcon : null), + [CustomIcon] + ) + + const ErrorIcon = React.useMemo( + () => (CustomErrorIcon ? CustomErrorIcon : null), + [CustomErrorIcon] + ) + + const Check = React.useMemo( + () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon), + [CustomCheckIcon] + ) + + return React.useMemo(() => { + if (isCompletedStep) { + if (isError && isKeepError) { + return ( +
+ +
+ ) + } + return ( +
+ +
+ ) + } + if (isCurrentStep) { + if (isError && ErrorIcon) { + return ( +
+ +
+ ) + } + if (isError) { + return ( +
+ +
+ ) + } + if (isLoading) { + return ( + + ) + } + } + if (Icon) { + return ( +
+ +
+ ) + } + return ( + + {(index || 0) + 1} + + ) + }, [ + isCompletedStep, + isCurrentStep, + isError, + isLoading, + Icon, + index, + Check, + ErrorIcon, + isKeepError, + ref, + size, + ]) + } +) + +// <---------- STEP LABEL ----------> + +interface StepLabelProps { + isCurrentStep?: boolean + opacity: number + label?: string | React.ReactNode + description?: string | null +} + +const labelVariants = cva("", { + variants: { + size: { + sm: "text-sm", + md: "text-sm", + lg: "text-base", + }, + }, + defaultVariants: { + size: "md", + }, +}) + +const descriptionVariants = cva("", { + variants: { + size: { + sm: "text-xs", + md: "text-xs", + lg: "text-sm", + }, + }, + defaultVariants: { + size: "md", + }, +}) + +const StepLabel = ({ + isCurrentStep, + opacity, + label, + description, +}: StepLabelProps) => { + const { variant, styles, size, orientation } = useStepper() + const shouldRender = !!label || !!description + + return shouldRender ? ( +
+ {!!label && ( + + {label} + + )} + {!!description && ( + + {description} + + )} +
+ ) : null +} + +export { Stepper, Step, useStepper } +export type { StepProps, StepperProps, StepItem } diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx index 807b18eebcd..4a2d7384752 100644 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -1,71 +1,78 @@ import { Button } from "@/registry/new-york/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/new-york/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2", optional: true }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- - {steps.map((step, index) => { + alert("You clicked on a step!")} + > + {steps.map(({ label }, index) => { return ( - - alert(`You clicked on step ${index + 1}!`) - } - > -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx index 9676f7d1e2c..1d42663bd55 100644 --- a/apps/www/registry/new-york/example/stepper-custom-icons.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-icons.tsx @@ -1,68 +1,76 @@ -import { Calendar, Lock, User } from "lucide-react" +import { Building, Star, User } from "lucide-react" import { Button } from "@/registry/new-york/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/new-york/ui/stepper" const steps = [ - { id: 0, label: "Step 1", icon: }, - { id: 1, label: "Step 2", icon: }, - { id: 2, label: "Step 3", icon: }, -] + { label: "Step 1", icon: User }, + { label: "Step 2", icon: Building }, + { label: "Step 3", icon: Star }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label, icon }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index 79d8982f916..b6f6ff91b17 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/new-york/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/new-york/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2" }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/new-york/example/stepper-description.tsx b/apps/www/registry/new-york/example/stepper-description.tsx index 76cc347de25..f45ee509480 100644 --- a/apps/www/registry/new-york/example/stepper-description.tsx +++ b/apps/www/registry/new-york/example/stepper-description.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/new-york/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/new-york/ui/stepper" const steps = [ - { id: 0, label: "Step 1", description: "Description 1" }, - { id: 1, label: "Step 2", description: "Description 2" }, - { id: 2, label: "Step 3", description: "Description 3" }, -] + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, + { label: "Step 3", description: "Description 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label, description }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/new-york/example/stepper-footer-inside.tsx b/apps/www/registry/new-york/example/stepper-footer-inside.tsx new file mode 100644 index 00000000000..446f8ae25b1 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-footer-inside.tsx @@ -0,0 +1,74 @@ +import { Button } from "@/registry/new-york/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/new-york/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ) + })} + +
+
+ ) +} + +const StepButtons = () => { + const { nextStep, prevStep, activeStep, isLastStep, isOptional } = + useStepper() + return ( +
+ + +
+ ) +} + +const FinalStep = () => { + const { hasCompletedAllSteps, reset } = useStepper() + + if (!hasCompletedAllSteps) { + return null + } + + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ) +} diff --git a/apps/www/registry/new-york/example/stepper-form.tsx b/apps/www/registry/new-york/example/stepper-form.tsx index b752cfd76d1..d69c35bd4bf 100644 --- a/apps/www/registry/new-york/example/stepper-form.tsx +++ b/apps/www/registry/new-york/example/stepper-form.tsx @@ -15,12 +15,7 @@ import { FormMessage, } from "@/registry/new-york/ui/form" import { Input } from "@/registry/new-york/ui/input" -import { - Stepper, - StepperFooter, - StepperItem, - useStepper, -} from "@/registry/new-york/ui/stepper" +import { Step, Stepper, useStepper } from "@/registry/new-york/ui/stepper" import { toast } from "@/registry/new-york/ui/use-toast" const steps = [ @@ -31,24 +26,30 @@ const steps = [ export default function StepperDemo() { return (
- + {steps.map((step, index) => { if (index === 0) { return ( - + - + ) } return ( - + - + ) })} - - - +
) @@ -137,9 +138,7 @@ function SecondStepForm() { - - This is your private password. - + This is your private password. )} @@ -152,28 +151,33 @@ function SecondStepForm() { function StepperFormActions() { const { + nextStep, + prevStep, + reset, activeStep, + hasCompletedAllSteps, isLastStep, - isOptionalStep, - isDisabledStep, - prevStep, - resetSteps, - steps, + isOptional, } = useStepper() return ( -
- {activeStep === steps.length ? ( - <> - - +
+ {hasCompletedAllSteps ? ( + ) : ( <> - - )} @@ -182,7 +186,7 @@ function StepperFormActions() { } function MyStepperFooter() { - const { activeStep, resetSteps, steps } = useStepper() + const { activeStep, reset, steps } = useStepper() if (activeStep !== steps.length) { return null @@ -190,7 +194,7 @@ function MyStepperFooter() { return (
- +
) } diff --git a/apps/www/registry/new-york/example/stepper-label-orientation.tsx b/apps/www/registry/new-york/example/stepper-label-orientation.tsx deleted file mode 100644 index af6facad927..00000000000 --- a/apps/www/registry/new-york/example/stepper-label-orientation.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Stepper, - StepperFooter, - StepperItem, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { id: 0, label: "Step 1", description: "Description 1" }, - { id: 1, label: "Step 2", description: "Description 2" }, - { id: 2, label: "Step 3", description: "Description 3" }, -] - -export default function StepperDemo() { - return ( -
- - {steps.map((step, index) => { - return ( - -
-

Step {index + 1} content

-
-
- ) - })} - - - -
-
- ) -} - -function MyStepperFooter() { - const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, - nextStep, - prevStep, - resetSteps, - steps, - } = useStepper() - - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - - )} -
- ) -} diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx index 5126c15d8a7..d2965006a7d 100644 --- a/apps/www/registry/new-york/example/stepper-optional-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-optional-steps.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/new-york/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/new-york/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2", optional: true }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2", optional: true }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map(({ label }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index be6c1a2a686..2ed096e9bd1 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -1,66 +1,74 @@ import { Button } from "@/registry/new-york/ui/button" import { + Step, + StepItem, Stepper, - StepperFooter, - StepperItem, useStepper, } from "@/registry/new-york/ui/stepper" const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2" }, - { id: 2, label: "Step 3" }, -] + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] export default function StepperDemo() { return (
- - {steps.map((step, index) => { + + {steps.map(({ label }, index) => { return ( - -
-

Step {index + 1} content

+ +
+

Step {index + 1}

- +
) })} - - - +
) } -function MyStepperFooter() { +const Footer = () => { const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, nextStep, prevStep, - resetSteps, - steps, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, } = useStepper() - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
)} -
+
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ ) } diff --git a/apps/www/registry/new-york/example/stepper-state.tsx b/apps/www/registry/new-york/example/stepper-state.tsx new file mode 100644 index 00000000000..db4599209c6 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-state.tsx @@ -0,0 +1,75 @@ +import { Button } from "@/registry/new-york/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/new-york/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, + isError, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/new-york/example/stepper-status.tsx b/apps/www/registry/new-york/example/stepper-status.tsx deleted file mode 100644 index 93710f0293a..00000000000 --- a/apps/www/registry/new-york/example/stepper-status.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useState } from "react" - -import { Button } from "@/registry/new-york/ui/button" -import { Label } from "@/registry/new-york/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" -import { - Stepper, - StepperFooter, - StepperItem, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { id: 0, label: "Step 1" }, - { id: 1, label: "Step 2" }, - { id: 2, label: "Step 3" }, -] - -export default function StepperStates() { - const [value, setValue] = useState<"loading" | "error">("loading") - - return ( -
- setValue(value as "loading" | "error")} - className="mb-4" - > -
- - -
-
- - -
-
- -
- ) -} - -function StepperDemo({ - status = "default", -}: { - status?: "default" | "loading" | "error" -}) { - return ( -
- - {steps.map((step, index) => { - return ( - -
-

Step {index + 1} content

-
-
- ) - })} - - - -
-
- ) -} - -function MyStepperFooter() { - const { - activeStep, - isLastStep, - isOptionalStep, - isDisabledStep, - nextStep, - prevStep, - resetSteps, - steps, - } = useStepper() - - return ( -
- {activeStep === steps.length ? ( - <> - - - ) : ( - <> - - - - )} -
- ) -} diff --git a/apps/www/registry/new-york/example/stepper-variants.tsx b/apps/www/registry/new-york/example/stepper-variants.tsx new file mode 100644 index 00000000000..7828ca3920b --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-variants.tsx @@ -0,0 +1,100 @@ +import * as React from "react" + +import { Button } from "@/registry/new-york/ui/button" +import { Label } from "@/registry/new-york/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" +import { + Step, + StepItem, + Stepper, + useStepper, + type StepperProps, +} from "@/registry/new-york/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + const [variant, setVariant] = + React.useState("circles") + + return ( +
+ setVariant(value as StepperProps["variant"])} + > +
+ + +
+
+ + +
+
+ + +
+
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 3fa2acf6812..0ca80eff5af 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -1,60 +1,55 @@ -import React from "react" +import * as React from "react" import { cva } from "class-variance-authority" -import { Check, Loader2, X } from "lucide-react" +import { CheckIcon, Loader2, LucideIcon, X } from "lucide-react" import { cn } from "@/lib/utils" - -import { Button } from "./button" -import { Separator } from "./separator" - -interface StepperProps { - steps: { - id: string | number - label: string | React.ReactNode - description?: string | React.ReactNode - icon?: React.ReactNode - optional?: boolean - }[] - initialStep: number - orientation?: "vertical" | "horizontal" - labelOrientation?: "vertical" | "horizontal" - scrollTracking?: boolean - variant?: "default" | "ghost" | "outline" | "secondary" - status?: "default" | "success" | "error" | "loading" - isClickable?: boolean -} - -interface ContextStepperProps extends StepperProps { - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void +import { Button } from "@/registry/new-york/ui/button" +import { + Collapsible, + CollapsibleContent, +} from "@/registry/new-york/ui/collapsible" + +// <---------- CONTEXT ----------> + +interface StepperContextValue extends StepperProps { + clickable?: boolean + isError?: boolean + isLoading?: boolean + isVertical?: boolean + stepCount?: number + expandVerticalSteps?: boolean activeStep: number - setStatus: (status: StepperProps["status"]) => void + initialStep: number } -const StepperContext = React.createContext({ +const StepperContext = React.createContext< + StepperContextValue & { + nextStep: () => void + prevStep: () => void + reset: () => void + setStep: (step: number) => void + } +>({ steps: [], + activeStep: 0, initialStep: 0, nextStep: () => {}, prevStep: () => {}, - resetSteps: () => {}, + reset: () => {}, setStep: () => {}, - activeStep: 0, - setStatus: () => {}, }) -const StepperProvider = ({ - value, - children, -}: { - value: StepperProps +type StepperContextProviderProps = { + value: Omit children: React.ReactNode -}) => { +} + +const StepperProvider = ({ value, children }: StepperContextProviderProps) => { + const isError = value.state === "error" + const isLoading = value.state === "loading" + const [activeStep, setActiveStep] = React.useState(value.initialStep) - const [status, setStatus] = React.useState( - value.status - ) + const nextStep = () => { setActiveStep((prev) => prev + 1) } @@ -63,7 +58,7 @@ const StepperProvider = ({ setActiveStep((prev) => prev - 1) } - const resetSteps = () => { + const reset = () => { setActiveStep(value.initialStep) } @@ -75,13 +70,13 @@ const StepperProvider = ({ {children} @@ -89,289 +84,891 @@ const StepperProvider = ({ ) } -export function useStepper() { +// <---------- HOOKS ----------> + +function useStepper() { const context = React.useContext(StepperContext) if (context === undefined) { throw new Error("useStepper must be used within a StepperProvider") } - const isDisabledStep = context.activeStep === 0 + const { children, className, ...rest } = context + const isLastStep = context.activeStep === context.steps.length - 1 - const isOptionalStep = context.steps[context.activeStep]?.optional - const isFinished = context.activeStep === context.steps.length + const hasCompletedAllSteps = context.activeStep === context.steps.length + + const currentStep = context.steps[context.activeStep] + const isOptional = currentStep?.optional return { - ...context, - isDisabledStep, + ...rest, isLastStep, - isOptionalStep, - isFinished, + hasCompletedAllSteps, + isOptional, + currentStep, + } +} + +function useMediaQuery(query: string) { + const [value, setValue] = React.useState(false) + + React.useEffect(() => { + function onChange(event: MediaQueryListEvent) { + setValue(event.matches) + } + + const result = matchMedia(query) + result.addEventListener("change", onChange) + setValue(result.matches) + + return () => result.removeEventListener("change", onChange) + }, [query]) + + return value +} + +// <---------- STEPS ----------> + +type StepItem = { + label?: string + description?: string + icon?: IconType + optional?: boolean +} + +interface StepOptions { + orientation?: "vertical" | "horizontal" + state?: "loading" | "error" + responsive?: boolean + checkIcon?: IconType + errorIcon?: IconType + onClickStep?: (step: number) => void + responsiveBreakpoint?: string + variant?: "circles" | "circles-alt" | "simple" + expandVerticalSteps?: boolean + size?: "sm" | "md" | "lg" + styles?: { + /** Styles for the main container */ + "main-container"?: string + /** Styles for the horizontal step */ + "horizontal-step"?: string + /** Styles for the horizontal step container (button and labels) */ + "horizontal-step-container"?: string + /** Styles for the vertical step */ + "vertical-step"?: string + /** Styles for the vertical step container (button and labels) */ + "vertical-step-container"?: string + /** Styles for the vertical step content */ + "vertical-step-content"?: string + /** Styles for the step button container */ + "step-button-container"?: string + /** Styles for the label and description container */ + "step-label-container"?: string + /** Styles for the step label */ + "step-label"?: string + /** Styles for the step description */ + "step-description"?: string } + scrollTracking?: boolean +} +interface StepperProps extends StepOptions { + children?: React.ReactNode + className?: string + initialStep: number + steps: StepItem[] } -export const Stepper = React.forwardRef< - HTMLDivElement, - StepperProps & React.HTMLAttributes ->( - ( - { - initialStep, +const VARIABLE_SIZES = { + sm: "1.75rem", + md: "2rem", + lg: "2.25rem", +} + +const Stepper = React.forwardRef( + (props, ref: React.Ref) => { + const { + className, + children, + orientation: orientationProp, + state, + responsive, + checkIcon, + errorIcon, + onClickStep, + responsiveBreakpoint, + expandVerticalSteps = false, + initialStep = 0, + size, steps, - status = "default", - orientation = "horizontal", - labelOrientation = "horizontal", + variant, + styles, scrollTracking = false, - children, - variant = "default", - isClickable = true, - className, - ...props - }, - ref - ) => { - const footer = [] as React.ReactElement[] + ...rest + } = props + + const childArr = React.Children.toArray(children) - const items = React.Children.toArray(children).map((child, index) => { + const items = [] as React.ReactElement[] + + const footer = childArr.map((child, _index) => { if (!React.isValidElement(child)) { throw new Error("Stepper children must be valid React elements.") } - if (child.type === StepperFooter) { - footer.push(child) + if (child.type === Step) { + items.push(child) return null } - const stepperItemProps = { - ...child.props, - step: index, - } - return React.cloneElement(child, stepperItemProps) + return child }) + const stepCount = items.length + + const isMobile = useMediaQuery( + `(max-width: ${responsiveBreakpoint || "768px"})` + ) + + const clickable = !!onClickStep + + const orientation = isMobile && responsive ? "vertical" : orientationProp + + const isVertical = orientation === "vertical" + return ( -
-
- {items} -
- {orientation === "horizontal" && ( - {children} +
+ {items}
+ {orientation === "horizontal" && ( + {items} + )} + {footer} ) } ) -const HorizontalContent = ({ children }: { children?: React.ReactNode }) => { - const { activeStep, isFinished } = useStepper() +Stepper.defaultProps = { + size: "md", + orientation: "horizontal", + responsive: true, +} - if (isFinished) { - return null - } +const VerticalContent = ({ children }: { children: React.ReactNode }) => { + const { activeStep } = useStepper() - const activeStepperItem = React.Children.toArray(children)[ - activeStep - ] as React.ReactElement + const childArr = React.Children.toArray(children) + const stepCount = childArr.length - const content = activeStepperItem?.props?.children + return ( + <> + {React.Children.map(children, (child, i) => { + const isCompletedStep = + (React.isValidElement(child) && + (child.props as any).isCompletedStep) ?? + i < activeStep + const isLastStep = i === stepCount - 1 + const isCurrentStep = i === activeStep + + const stepProps = { + index: i, + isCompletedStep, + isCurrentStep, + isLastStep, + } - return content + if (React.isValidElement(child)) { + return React.cloneElement(child, stepProps) + } + return null + })} + + ) } -const stepperItemVariants = cva("relative flex flex-row gap-2", { - variants: { - isLastStep: { - true: "flex-[0_0_auto] justify-end", - false: "flex-[1_0_auto] justify-start", - }, - isVertical: { - true: "flex-col", - false: "items-center", - }, - }, - compoundVariants: [ - { - isVertical: true, - isLastStep: true, - class: "w-full flex-[1_0_auto] flex-col items-start justify-start", - }, - ], -}) - -const icons = { - success: , - error: , - loading: , - default: null, -} as const - -export const StepperItem = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & { - onStepperItemClick?: ( - e: React.MouseEvent - ) => void +const HorizontalContent = ({ children }: { children: React.ReactNode }) => { + const { activeStep } = useStepper() + const childArr = React.Children.toArray(children) + + const renderHorizontalContent = () => { + if (activeStep <= childArr.length) { + return React.Children.map(childArr[activeStep], (node) => { + if (!React.isValidElement(node)) { + return + } + return React.Children.map(node.props.children, (childNode) => childNode) + }) + } + return null } - // @ts-ignore - step is a prop that is added from the Stepper through React.Children. ->(({ step, children, className, onStepperItemClick, ...props }, ref) => { - const { - activeStep, - setStep, - steps, - orientation, - labelOrientation, - scrollTracking, - variant, - status, - isClickable, - } = useStepper() - const isActive = step === activeStep - const isCompleted = step < activeStep - const isDisabled = step > activeStep && !isClickable + return <>{renderHorizontalContent()} +} - const isLastStep = step === steps.length - 1 +// <---------- STEP ----------> + +interface StepProps extends React.HTMLAttributes { + label?: string | React.ReactNode + description?: string + icon?: IconType + state?: "loading" | "error" + checkIcon?: IconType + errorIcon?: IconType + isCompletedStep?: boolean + isKeepError?: boolean + onClickStep?: (step: number) => void +} - const isVertical = orientation === "vertical" - const isVerticalLabel = labelOrientation === "vertical" +interface StepSharedProps extends StepProps { + isLastStep?: boolean + isCurrentStep?: boolean + index?: number + hasVisited: boolean | undefined + isError?: boolean + isLoading?: boolean +} - const isError = isActive && status === "error" +// Props which shouldn't be passed to to the Step component from the user +interface StepInternalConfig { + index: number + isCompletedStep?: boolean + isCurrentStep?: boolean + isLastStep?: boolean +} - let icon = steps[step].icon || step + 1 - if (status !== "default" && isActive) { - icon = icons[status!] - } - if (isCompleted) { - icon = icons.success +interface FullStepProps extends StepProps, StepInternalConfig {} + +const Step = React.forwardRef( + (props, ref: React.Ref) => { + const { + children, + description, + icon, + state, + checkIcon, + errorIcon, + index, + isCompletedStep, + isCurrentStep, + isLastStep, + isKeepError, + label, + onClickStep, + } = props as FullStepProps + + const { isVertical, isError, isLoading, clickable } = useStepper() + + const hasVisited = isCurrentStep || isCompletedStep + + const sharedProps = { + isLastStep, + isCompletedStep, + isCurrentStep, + index, + isError, + isLoading, + clickable, + label, + description, + hasVisited, + icon, + isKeepError, + checkIcon, + state, + errorIcon, + onClickStep, + } + + const renderStep = () => { + switch (isVertical) { + case true: + return ( + + {children} + + ) + default: + return + } + } + + return renderStep() } +) - const content = React.Children.toArray(children).filter( - (child) => React.isValidElement(child) && child.type !== StepperFooter - ) +// <---------- VERTICAL STEP ----------> - const footer = React.Children.toArray(children).filter( - (child) => React.isValidElement(child) && child.type === StepperFooter - )[0] as React.ReactElement +type VerticalStepProps = StepSharedProps & { + children?: React.ReactNode +} - const onClickItem = (e: React.MouseEvent) => { - if (isDisabled) { - return - } - if (onStepperItemClick) { - return onStepperItemClick(e) - } - setStep(step) +const verticalStepVariants = cva( + "flex flex-col relative transition-all duration-200", + { + variants: { + variant: { + circles: cn( + "pb-[var(--step-gap)] gap-[var(--step-gap)]", + "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", + "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", + "[&:not(:last-child)]:after:absolute", + "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]", + "[&:not(:last-child)]:after:bottom-[var(--step-gap)]", + "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200" + ), + simple: "flex-1 border-t-0 mb-4", + }, + }, } +) - return ( -
+const VerticalStep = React.forwardRef( + (props, ref) => { + const { + children, + index, + isCompletedStep, + isCurrentStep, + label, + description, + icon, + hasVisited, + state, + checkIcon: checkIconProp, + errorIcon: errorIconProp, + onClickStep, + } = props + + const { + checkIcon: checkIconContext, + errorIcon: errorIconContext, + isError, + isLoading, + variant, + onClickStep: onClickStepGeneral, + clickable, + expandVerticalSteps, + styles, + scrollTracking, + orientation, + } = useStepper() + + const opacity = hasVisited ? 1 : 0.8 + const localIsLoading = isLoading || state === "loading" + const localIsError = isError || state === "error" + + const active = + variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + const checkIcon = checkIconProp || checkIconContext + const errorIcon = errorIconProp || errorIconContext + + const renderChildren = () => { + if (!expandVerticalSteps) { + return ( + + + {children} + + + ) + } + return children + } + + return (
{ - if (scrollTracking) { - node?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }} + data-active={active} + data-clickable={clickable || !!onClickStep} + data-invalid={localIsError} + onClick={() => + onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0) + } > - + + + + +
{ + if (scrollTracking) { + node?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + }} className={cn( - "w-max text-start", - isVerticalLabel && !isVertical && "text-center" + "stepper__vertical-step-content", + "min-h-4", + variant !== "simple" && "ps-[--step-icon-size]", + variant === "simple" && orientation === "vertical" && "min-h-0", + styles?.["vertical-step-content"] )} > -

- {steps[step].label} -

- {steps[step].description && ( -

- {steps[step].description} -

- )} + {renderChildren()}
-
- {!isLastStep && ( - - )} - {isVertical && isActive && ( -
- {content} - {footer} -
+ ) + } +) + +// <---------- HORIZONTAL STEP ----------> + +const HorizontalStep = React.forwardRef( + (props, ref) => { + const { + isError, + isLoading, + onClickStep, + variant, + clickable, + checkIcon: checkIconContext, + errorIcon: errorIconContext, + styles, + } = useStepper() + + const { + index, + isCompletedStep, + isCurrentStep, + hasVisited, + icon, + label, + description, + isKeepError, + state, + checkIcon: checkIconProp, + errorIcon: errorIconProp, + } = props + + const localIsLoading = isLoading || state === "loading" + const localIsError = isError || state === "error" + + const opacity = hasVisited ? 1 : 0.8 + + const active = + variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + + const checkIcon = checkIconProp || checkIconContext + const errorIcon = errorIconProp || errorIconContext + + return ( +
onClickStep?.(index || 0)} + ref={ref} + > +
+ + + + +
-
- ) + ) + } +) + +// <---------- STEP BUTTON CONTAINER ----------> + +type StepButtonContainerProps = StepSharedProps & { + children?: React.ReactNode +} + +const stepButtonVariants = cva("", { + variants: { + size: { + sm: "w-8 h-8", + md: "w-9 h-9", + lg: "w-10 h-10", + }, + }, + defaultVariants: { + size: "md", + }, }) -StepperItem.displayName = "StepperItem" +const StepButtonContainer = ({ + isCurrentStep, + isCompletedStep, + children, + isError, + isLoading: isLoadingProp, +}: StepButtonContainerProps) => { + const { + clickable, + isLoading: isLoadingContext, + variant, + styles, + size, + } = useStepper() + + const isLoading = isLoadingProp || isLoadingContext + + if (variant === "simple") { + return null + } -export const StepperFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ children, ...props }, ref) => { return ( -
+
+ ) +} + +// <---------- STEP ICON ----------> + +type IconType = LucideIcon | React.ComponentType | undefined + +const iconVariants = cva("", { + variants: { + size: { + sm: "size-4", + md: "size-4", + lg: "size-5", + }, + }, + defaultVariants: { + size: "md", + }, }) -StepperFooter.displayName = "StepperFooter" +interface StepIconProps { + isCompletedStep?: boolean + isCurrentStep?: boolean + isError?: boolean + isLoading?: boolean + isKeepError?: boolean + icon?: IconType + index?: number + checkIcon?: IconType + errorIcon?: IconType +} + +const StepIcon = React.forwardRef( + (props, ref) => { + const { size } = useStepper() + + const { + isCompletedStep, + isCurrentStep, + isError, + isLoading, + isKeepError, + icon: CustomIcon, + index, + checkIcon: CustomCheckIcon, + errorIcon: CustomErrorIcon, + } = props + + const Icon = React.useMemo( + () => (CustomIcon ? CustomIcon : null), + [CustomIcon] + ) + + const ErrorIcon = React.useMemo( + () => (CustomErrorIcon ? CustomErrorIcon : null), + [CustomErrorIcon] + ) + + const Check = React.useMemo( + () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon), + [CustomCheckIcon] + ) + + return React.useMemo(() => { + if (isCompletedStep) { + if (isError && isKeepError) { + return ( +
+ +
+ ) + } + return ( +
+ +
+ ) + } + if (isCurrentStep) { + if (isError && ErrorIcon) { + return ( +
+ +
+ ) + } + if (isError) { + return ( +
+ +
+ ) + } + if (isLoading) { + return ( + + ) + } + } + if (Icon) { + return ( +
+ +
+ ) + } + return ( + + {(index || 0) + 1} + + ) + }, [ + isCompletedStep, + isCurrentStep, + isError, + isLoading, + Icon, + index, + Check, + ErrorIcon, + isKeepError, + ref, + size, + ]) + } +) + +// <---------- STEP LABEL ----------> + +interface StepLabelProps { + isCurrentStep?: boolean + opacity: number + label?: string | React.ReactNode + description?: string | null +} + +const labelVariants = cva("", { + variants: { + size: { + sm: "text-sm", + md: "text-sm", + lg: "text-base", + }, + }, + defaultVariants: { + size: "md", + }, +}) + +const descriptionVariants = cva("", { + variants: { + size: { + sm: "text-xs", + md: "text-xs", + lg: "text-sm", + }, + }, + defaultVariants: { + size: "md", + }, +}) + +const StepLabel = ({ + isCurrentStep, + opacity, + label, + description, +}: StepLabelProps) => { + const { variant, styles, size, orientation } = useStepper() + const shouldRender = !!label || !!description + + return shouldRender ? ( +
+ {!!label && ( + + {label} + + )} + {!!description && ( + + {description} + + )} +
+ ) : null +} + +export { Stepper, Step, useStepper } +export type { StepProps, StepperProps, StepItem } diff --git a/apps/www/registry/registry.ts b/apps/www/registry/registry.ts index aa1f8ce5ea1..ae99963657f 100644 --- a/apps/www/registry/registry.ts +++ b/apps/www/registry/registry.ts @@ -906,6 +906,12 @@ const example: Registry = [ registryDependencies: ["stepper"], files: ["example/stepper-form.tsx"], }, + { + name: "stepper-variants", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-variants.tsx"], + }, { name: "stepper-custom-icons", type: "components:example", @@ -913,10 +919,10 @@ const example: Registry = [ files: ["example/stepper-custom-icons.tsx"], }, { - name: "stepper-label-orientation", + name: "stepper-footer-inside", type: "components:example", registryDependencies: ["stepper"], - files: ["example/stepper-label-orientation.tsx"], + files: ["example/stepper-footer-inside.tsx"], }, { name: "stepper-clickable-steps", @@ -931,10 +937,10 @@ const example: Registry = [ files: ["example/stepper-optional-steps.tsx"], }, { - name: "stepper-status", + name: "stepper-state", type: "components:example", registryDependencies: ["stepper"], - files: ["example/stepper-status.tsx"], + files: ["example/stepper-state.tsx"], }, { name: "switch-demo", diff --git a/tailwind.config.cjs b/tailwind.config.cjs index fc55679139a..55f1ab1369a 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -67,6 +67,14 @@ module.exports = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, + "collapsible-down": { + from: { height: "0" }, + to: { height: "var(--radix-collapsible-content-height)" }, + }, + "collapsible-up": { + from: { height: "var(--radix-collapsible-content-height)" }, + to: { height: "0" }, + }, "caret-blink": { "0%,70%,100%": { opacity: "1" }, "20%,50%": { opacity: "0" }, @@ -76,6 +84,8 @@ module.exports = { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", "caret-blink": "caret-blink 1.25s ease-out infinite", + "collapsible-down": "collapsible-down 0.2s ease-out", + "collapsible-up": "collapsible-up 0.2s ease-out", }, }, }, From 277403bf77e2501f70903d1a3c03b56fa8f1cd9f Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 22 Mar 2024 16:24:52 -0300 Subject: [PATCH 19/62] chore: update registry with new format --- apps/www/__registry__/index.tsx | 44 ++++++++++++++ apps/www/content/docs/components/stepper.mdx | 1 - apps/www/registry/examples.ts | 60 ++++++++++++++++++++ apps/www/registry/registry.ts | 2 +- apps/www/registry/ui.ts | 5 ++ 5 files changed, 110 insertions(+), 2 deletions(-) diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index f6755859bd9..a13be653c00 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -344,6 +344,8 @@ export const Index: Record = { registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/stepper")), files: ["registry/default/ui/stepper.tsx"], + category: "undefined", + subcategory: "undefined", }, "switch": { name: "switch", @@ -1314,6 +1316,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-demo")), files: ["registry/default/example/stepper-demo.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-orientation": { name: "stepper-orientation", @@ -1321,6 +1325,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-orientation")), files: ["registry/default/example/stepper-orientation.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-description": { name: "stepper-description", @@ -1328,6 +1334,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-description")), files: ["registry/default/example/stepper-description.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-form": { name: "stepper-form", @@ -1335,6 +1343,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-form")), files: ["registry/default/example/stepper-form.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-variants": { name: "stepper-variants", @@ -1342,6 +1352,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-variants")), files: ["registry/default/example/stepper-variants.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-custom-icons": { name: "stepper-custom-icons", @@ -1349,6 +1361,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-custom-icons")), files: ["registry/default/example/stepper-custom-icons.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-footer-inside": { name: "stepper-footer-inside", @@ -1356,6 +1370,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-footer-inside")), files: ["registry/default/example/stepper-footer-inside.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-clickable-steps": { name: "stepper-clickable-steps", @@ -1363,6 +1379,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-clickable-steps")), files: ["registry/default/example/stepper-clickable-steps.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-optional-steps": { name: "stepper-optional-steps", @@ -1370,6 +1388,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-optional-steps")), files: ["registry/default/example/stepper-optional-steps.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-state": { name: "stepper-state", @@ -1377,6 +1397,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-state")), files: ["registry/default/example/stepper-state.tsx"], + category: "undefined", + subcategory: "undefined", }, "switch-demo": { name: "switch-demo", @@ -2186,6 +2208,8 @@ export const Index: Record = { registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/stepper")), files: ["registry/new-york/ui/stepper.tsx"], + category: "undefined", + subcategory: "undefined", }, "switch": { name: "switch", @@ -3156,6 +3180,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-demo")), files: ["registry/new-york/example/stepper-demo.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-orientation": { name: "stepper-orientation", @@ -3163,6 +3189,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-orientation")), files: ["registry/new-york/example/stepper-orientation.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-description": { name: "stepper-description", @@ -3170,6 +3198,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-description")), files: ["registry/new-york/example/stepper-description.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-form": { name: "stepper-form", @@ -3177,6 +3207,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-form")), files: ["registry/new-york/example/stepper-form.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-variants": { name: "stepper-variants", @@ -3184,6 +3216,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-variants")), files: ["registry/new-york/example/stepper-variants.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-custom-icons": { name: "stepper-custom-icons", @@ -3191,6 +3225,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-icons")), files: ["registry/new-york/example/stepper-custom-icons.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-footer-inside": { name: "stepper-footer-inside", @@ -3198,6 +3234,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-footer-inside")), files: ["registry/new-york/example/stepper-footer-inside.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-clickable-steps": { name: "stepper-clickable-steps", @@ -3205,6 +3243,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-clickable-steps")), files: ["registry/new-york/example/stepper-clickable-steps.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-optional-steps": { name: "stepper-optional-steps", @@ -3212,6 +3252,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-optional-steps")), files: ["registry/new-york/example/stepper-optional-steps.tsx"], + category: "undefined", + subcategory: "undefined", }, "stepper-state": { name: "stepper-state", @@ -3219,6 +3261,8 @@ export const Index: Record = { registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-state")), files: ["registry/new-york/example/stepper-state.tsx"], + category: "undefined", + subcategory: "undefined", }, "switch-demo": { name: "switch-demo", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 4a46542b67b..07190d2de55 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -271,4 +271,3 @@ styles?: { "step-description"?: string } ``` - diff --git a/apps/www/registry/examples.ts b/apps/www/registry/examples.ts index 480b2b61c60..7a90acd09b0 100644 --- a/apps/www/registry/examples.ts +++ b/apps/www/registry/examples.ts @@ -599,6 +599,66 @@ export const examples: Registry = [ registryDependencies: ["sonner"], files: ["example/sonner-demo.tsx"], }, + { + name: "stepper-demo", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-demo.tsx"], + }, + { + name: "stepper-orientation", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-orientation.tsx"], + }, + { + name: "stepper-description", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-description.tsx"], + }, + { + name: "stepper-form", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-form.tsx"], + }, + { + name: "stepper-variants", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-variants.tsx"], + }, + { + name: "stepper-custom-icons", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-custom-icons.tsx"], + }, + { + name: "stepper-footer-inside", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-footer-inside.tsx"], + }, + { + name: "stepper-clickable-steps", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-clickable-steps.tsx"], + }, + { + name: "stepper-optional-steps", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-optional-steps.tsx"], + }, + { + name: "stepper-state", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-state.tsx"], + }, { name: "switch-demo", type: "components:example", diff --git a/apps/www/registry/registry.ts b/apps/www/registry/registry.ts index 605888f90f9..5801d197bc1 100644 --- a/apps/www/registry/registry.ts +++ b/apps/www/registry/registry.ts @@ -3,4 +3,4 @@ import { examples } from "@/registry/examples" import { Registry } from "@/registry/schema" import { ui } from "@/registry/ui" -export const registry: Registry = [...ui, ...examples, ...blocks] \ No newline at end of file +export const registry: Registry = [...ui, ...examples, ...blocks] diff --git a/apps/www/registry/ui.ts b/apps/www/registry/ui.ts index e7cac119c02..84fe17f69f5 100644 --- a/apps/www/registry/ui.ts +++ b/apps/www/registry/ui.ts @@ -229,6 +229,11 @@ export const ui: Registry = [ dependencies: ["sonner", "next-themes"], files: ["ui/sonner.tsx"], }, + { + name: "stepper", + type: "components:ui", + files: ["ui/stepper.tsx"], + }, { name: "switch", type: "components:ui", From a8d08d2402af31cb1e015078d4fc068b200d5aa3 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 22 Mar 2024 19:20:22 -0300 Subject: [PATCH 20/62] chore: add sizes example --- apps/www/__registry__/index.tsx | 18 ++++ apps/www/content/docs/components/stepper.mdx | 6 ++ .../default/example/stepper-sizes.tsx | 99 +++++++++++++++++++ apps/www/registry/examples.ts | 6 ++ .../new-york/example/stepper-sizes.tsx | 99 +++++++++++++++++++ 5 files changed, 228 insertions(+) create mode 100644 apps/www/registry/default/example/stepper-sizes.tsx create mode 100644 apps/www/registry/new-york/example/stepper-sizes.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index a13be653c00..e6109bc807f 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -1337,6 +1337,15 @@ export const Index: Record = { category: "undefined", subcategory: "undefined", }, + "stepper-sizes": { + name: "stepper-sizes", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-sizes")), + files: ["registry/default/example/stepper-sizes.tsx"], + category: "undefined", + subcategory: "undefined", + }, "stepper-form": { name: "stepper-form", type: "components:example", @@ -3201,6 +3210,15 @@ export const Index: Record = { category: "undefined", subcategory: "undefined", }, + "stepper-sizes": { + name: "stepper-sizes", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-sizes")), + files: ["registry/new-york/example/stepper-sizes.tsx"], + category: "undefined", + subcategory: "undefined", + }, "stepper-form": { name: "stepper-form", type: "components:example", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 07190d2de55..72509bcd79d 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -188,6 +188,12 @@ There are 3 design variants for the Stepper component: +### Sizes + +The Stepper component has 3 sizes: `sm`, `md`, and `lg` which can be set using the `size` prop. + + + ### Responsive By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the `responsiveBreakpoint` prop. diff --git a/apps/www/registry/default/example/stepper-sizes.tsx b/apps/www/registry/default/example/stepper-sizes.tsx new file mode 100644 index 00000000000..ba388c32dd0 --- /dev/null +++ b/apps/www/registry/default/example/stepper-sizes.tsx @@ -0,0 +1,99 @@ +import * as React from "react" + +import { Button } from "@/registry/default/ui/button" +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" +import { + Step, + StepItem, + Stepper, + StepperProps, + useStepper, +} from "@/registry/default/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + const [size, setSize] = React.useState("md") + + return ( +
+ setSize(value as StepperProps["size"])} + > +
+ + +
+
+ + +
+
+ + +
+
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/examples.ts b/apps/www/registry/examples.ts index 7a90acd09b0..0ef0b277f96 100644 --- a/apps/www/registry/examples.ts +++ b/apps/www/registry/examples.ts @@ -617,6 +617,12 @@ export const examples: Registry = [ registryDependencies: ["stepper"], files: ["example/stepper-description.tsx"], }, + { + name: "stepper-sizes", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-sizes.tsx"], + }, { name: "stepper-form", type: "components:example", diff --git a/apps/www/registry/new-york/example/stepper-sizes.tsx b/apps/www/registry/new-york/example/stepper-sizes.tsx new file mode 100644 index 00000000000..e392fb4a7ed --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-sizes.tsx @@ -0,0 +1,99 @@ +import * as React from "react" + +import { Button } from "@/registry/new-york/ui/button" +import { Label } from "@/registry/new-york/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" +import { + Step, + StepItem, + Stepper, + StepperProps, + useStepper, +} from "@/registry/new-york/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + const [size, setSize] = React.useState("md") + + return ( +
+ setSize(value as StepperProps["size"])} + > +
+ + +
+
+ + +
+
+ + +
+
+ + {steps.map(({ label }, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + reset, + activeStep, + hasCompletedAllSteps, + isLastStep, + isOptional, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} From f0822022835a03178fb2b146ff18dbf213f31292 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 22 Mar 2024 19:27:45 -0300 Subject: [PATCH 21/62] chore: update prop name --- apps/www/content/docs/components/stepper.mdx | 2 +- apps/www/registry/default/ui/stepper.tsx | 6 +++--- apps/www/registry/new-york/ui/stepper.tsx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 72509bcd79d..786d2f7a3b4 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -196,7 +196,7 @@ The Stepper component has 3 sizes: `sm`, `md`, and `lg` which can be set using t ### Responsive -By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the `responsiveBreakpoint` prop. +By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the `mobileBreakpoint` prop. ### State diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 0cdc021626c..46af85428f0 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -144,7 +144,7 @@ interface StepOptions { checkIcon?: IconType errorIcon?: IconType onClickStep?: (step: number) => void - responsiveBreakpoint?: string + mobileBreakpoint?: string variant?: "circles" | "circles-alt" | "simple" expandVerticalSteps?: boolean size?: "sm" | "md" | "lg" @@ -196,7 +196,7 @@ const Stepper = React.forwardRef( checkIcon, errorIcon, onClickStep, - responsiveBreakpoint, + mobileBreakpoint, expandVerticalSteps = false, initialStep = 0, size, @@ -226,7 +226,7 @@ const Stepper = React.forwardRef( const stepCount = items.length const isMobile = useMediaQuery( - `(max-width: ${responsiveBreakpoint || "768px"})` + `(max-width: ${mobileBreakpoint || "768px"})` ) const clickable = !!onClickStep diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 0ca80eff5af..ecda712f4e7 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -144,7 +144,7 @@ interface StepOptions { checkIcon?: IconType errorIcon?: IconType onClickStep?: (step: number) => void - responsiveBreakpoint?: string + mobileBreakpoint?: string variant?: "circles" | "circles-alt" | "simple" expandVerticalSteps?: boolean size?: "sm" | "md" | "lg" @@ -196,7 +196,7 @@ const Stepper = React.forwardRef( checkIcon, errorIcon, onClickStep, - responsiveBreakpoint, + mobileBreakpoint, expandVerticalSteps = false, initialStep = 0, size, @@ -226,7 +226,7 @@ const Stepper = React.forwardRef( const stepCount = items.length const isMobile = useMediaQuery( - `(max-width: ${responsiveBreakpoint || "768px"})` + `(max-width: ${mobileBreakpoint || "768px"})` ) const clickable = !!onClickStep From 5fcf8bed1bbac6f2fc2fdad215356970c77f1d3b Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 22 Mar 2024 20:10:05 -0300 Subject: [PATCH 22/62] chore: add api docs --- apps/www/content/docs/components/stepper.mdx | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 786d2f7a3b4..c245865b7a1 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -277,3 +277,61 @@ styles?: { "step-description"?: string } ``` + +## API + +### Stepper + +| Prop | Type | Default | Description | +| --------------------- | ---------------------------------------- | ------------ | --------------------------------------------------------------------- | +| `initialStep*` | `number` | `-` | Currently active step | +| `steps*` | `StepItem[]` | `-` | An array of objects with the label and description of each step. | +| `orientation` | `"horizontal" \| "vertical"` | `horizontal` | The orientation of the stepper. | +| `size` | `"sm" \| "md" \| "lg"` | `md` | The size of the stepper. | +| `state` | `"loading" \| "error"` | `-` | The state of the stepper (global). | +| `icon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed in the step button. | +| `checkIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step is completed. | +| `errorIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step has an error. | +| `responsive` | `boolean` | `true` | If the stepper should be responsive. | +| `mobileBreakpoint` | `number` | `768px` | The breakpoint at which the stepper switches to vertical orientation. | +| `scrollTracking` | `boolean` | `false` | Scroll to the active step when scrolling forward or backward. | +| `styles` | `{ [key: string]: string }` | `-` | Custom styles for the stepper. | +| `onClickStep` | `(index: number) => void` | `-` | The function to be executed when a step is clicked. | +| `variant` | `"circles" \| "circles-alt" \| "simple"` | `circles` | The design variant of the stepper. | +| `expandVerticalSteps` | `boolean` | `false` | Control whether or not the vertical steps should collapse. | + +### Step + +| Prop | Type | Default | Description | +| ----------------- | ---------------------------------------- | ------- | ---------------------------------------------------------------------- | +| `label` | `string` | `-` | The label of the step. | +| `description` | `string` | `-` | The description of the step. | +| `optional` | `boolean` | `-` | If the step is optional. | +| `icon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed in the step button. | +| `state` | `"loading" \| "error"` | `-` | The state of the step (local). | +| `isCompletedStep` | `boolean` | `-` | Individually control each step state, defaults to active step | +| `isKeepError` | `boolean` | `-` | Individually control if each step should keep showing the error state. | +| `checkIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step is completed. | +| `errorIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step has an error. | + +### useStepper + +| Prop | Type | Description | +| ---------------------- | ------------------------- | ----------------------------------------- | +| `nextStep` | `() => void` | Function to go to the next step. | +| `prevStep` | `() => void` | Function to go to the previous step. | +| `setStep` | `(index: number) => void` | Function to set a specific step. | +| `reset` | `() => void` | Function to reset the stepper. | +| `activeStep` | `number` | The current active step. | +| `hasCompletedAllSteps` | `boolean` | If all steps have been completed. | +| `isLastStep` | `boolean` | If the current step is the last step. | +| `isOptional` | `boolean` | If the current step is optional. | +| `currentStep` | `StepItem` | The current step object. | +| `clickable` | `boolean` | If the steps are clickable. | +| `isError` | `boolean` | If the stepper has an error. | +| `isLoading` | `boolean` | If the stepper is loading. | +| `isVertical` | `boolean` | If the stepper is vertical. | +| `stepCount` | `number` | The total number of steps. | +| `expandVerticalSteps` | `boolean` | If the vertical steps should be expanded. | +| `activeStep` | `number` | The current active step. | +| `initialStep` | `number` | The initial active step. | From 535235d9d3843038c6e620e75fd0e792aa450dfa Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 22 Mar 2024 22:26:37 -0300 Subject: [PATCH 23/62] chore: add data attr docs --- apps/www/content/docs/components/stepper.mdx | 49 ++++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index c245865b7a1..6957b58cac6 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -247,36 +247,25 @@ You can also use the component with server actions. ### Custom styles -The Stepper component allows you to customize all the styles of each section that composes the component in terms of tailwind classes or custom classes that you want to define in CSS. - -Each name belongs to the general classes defined in each internal section of the component that are prefixed with `stepper__` followed by the class name. - -The following is the list of all the classes that can be customized through the `styles` prop: - -```ts -styles?: { - /** Styles for the main container */ - "main-container"?: string - /** Styles for the horizontal step */ - "horizontal-step"?: string - /** Styles for the horizontal step container (button and labels) */ - "horizontal-step-container"?: string - /** Styles for the vertical step */ - "vertical-step"?: string - /** Styles for the vertical step container (button and labels) */ - "vertical-step-container"?: string - /** Styles for the vertical step content */ - "vertical-step-content"?: string - /** Styles for the step button container */ - "step-button-container"?: string - /** Styles for the label and description container */ - "step-label-container"?: string - /** Styles for the step label */ - "step-label"?: string - /** Styles for the step description */ - "step-description"?: string -} -``` +To customize the styles of the Steps component, `Stepper` provides a list of css classes for each part of the component. You can use these classes to override the default styles. Below is a list of the classes that are available. + +- `main-container`: The main container of the stepper. +- `horizontal-step`: Outer wrapper for each step in horizontal layout +- `horizontal-step-container`: Inner wrapper for each step in horizontal layout +- `vertical-step`: Outer wrapper for each step in vertical layout +- `vertical-step-container`: Inner wrapper for each step in vertical layout +- `vertical-step-content`: Content wrapper for each step in vertical layout +- `step-button-container`: Wrapper for the step button +- `step-label-container`: Wrapper for the label and description +- `step-label`: The label of the step +- `step-description`: The description of the step + +In some cases you may want to customize the styles of a step based on its state. For example, you may want to change the color of a step when it is active. To do this, you can use the data attributes defined below. + +- `data-active`: The active step +- `data-invalid`: The step with an error +- `data-loading`: The step in loading state +- `data-clickable`: The step that is clickable ## API From 07c5e81639cf31f050e615dd21590963d64a990f Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 22 Mar 2024 22:36:34 -0300 Subject: [PATCH 24/62] chore: update data attr docs --- apps/www/content/docs/components/stepper.mdx | 2 ++ apps/www/registry/default/ui/stepper.tsx | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 6957b58cac6..00a96fc591f 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -266,6 +266,8 @@ In some cases you may want to customize the styles of a step based on its state. - `data-invalid`: The step with an error - `data-loading`: The step in loading state - `data-clickable`: The step that is clickable +- `data-completed`: The step that is completed +- `data-optional`: The step that is optional ## API diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 46af85428f0..dfa46206c7d 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -488,6 +488,7 @@ const VerticalStep = React.forwardRef( styles, scrollTracking, orientation, + steps } = useStepper() const opacity = hasVisited ? 1 : 0.8 @@ -524,6 +525,8 @@ const VerticalStep = React.forwardRef( "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", styles?.["vertical-step"] )} + data-optional={steps[index || 0]?.optional} + data-completed={isCompletedStep} data-active={active} data-clickable={clickable || !!onClickStep} data-invalid={localIsError} @@ -601,6 +604,7 @@ const HorizontalStep = React.forwardRef( checkIcon: checkIconContext, errorIcon: errorIconContext, styles, + steps, } = useStepper() const { @@ -647,6 +651,8 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", styles?.["horizontal-step"] )} + data-optional={steps[index || 0]?.optional} + data-completed={isCompletedStep} data-active={active} data-invalid={localIsError} data-clickable={clickable} From 2a137fb4376c58a21fbd5e02185a3554db519b65 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 25 Mar 2024 09:50:25 -0300 Subject: [PATCH 25/62] chore: improve API --- apps/www/content/docs/components/stepper.mdx | 33 ++++++++--------- .../example/stepper-clickable-steps.tsx | 16 ++++----- .../default/example/stepper-custom-icons.tsx | 16 ++++----- .../registry/default/example/stepper-demo.tsx | 16 ++++----- .../default/example/stepper-description.tsx | 16 ++++----- .../default/example/stepper-footer-inside.tsx | 18 +++++----- .../registry/default/example/stepper-form.tsx | 35 +++++++------------ .../example/stepper-optional-steps.tsx | 16 ++++----- .../default/example/stepper-orientation.tsx | 16 ++++----- .../default/example/stepper-sizes.tsx | 16 ++++----- .../default/example/stepper-state.tsx | 16 ++++----- .../default/example/stepper-variants.tsx | 16 ++++----- apps/www/registry/default/ui/stepper.tsx | 26 +++++++++----- .../example/stepper-clickable-steps.tsx | 16 ++++----- .../new-york/example/stepper-custom-icons.tsx | 16 ++++----- .../new-york/example/stepper-demo.tsx | 16 ++++----- .../new-york/example/stepper-description.tsx | 16 ++++----- .../example/stepper-footer-inside.tsx | 14 ++++---- .../new-york/example/stepper-form.tsx | 35 +++++++------------ .../example/stepper-optional-steps.tsx | 16 ++++----- .../new-york/example/stepper-orientation.tsx | 16 ++++----- .../new-york/example/stepper-sizes.tsx | 16 ++++----- .../new-york/example/stepper-state.tsx | 16 ++++----- .../new-york/example/stepper-variants.tsx | 16 ++++----- apps/www/registry/new-york/ui/stepper.tsx | 24 ++++++++----- 25 files changed, 236 insertions(+), 237 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 00a96fc591f..f45b5a4be67 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -113,11 +113,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -128,13 +128,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} @@ -295,6 +295,7 @@ In some cases you may want to customize the styles of a step based on its state. | Prop | Type | Default | Description | | ----------------- | ---------------------------------------- | ------- | ---------------------------------------------------------------------- | +| `id` | `string` | `-` | The id of the step. | | `label` | `string` | `-` | The label of the step. | | `description` | `string` | `-` | The description of the step. | | `optional` | `boolean` | `-` | If the step is optional. | @@ -309,20 +310,20 @@ In some cases you may want to customize the styles of a step based on its state. | Prop | Type | Description | | ---------------------- | ------------------------- | ----------------------------------------- | -| `nextStep` | `() => void` | Function to go to the next step. | -| `prevStep` | `() => void` | Function to go to the previous step. | -| `setStep` | `(index: number) => void` | Function to set a specific step. | -| `reset` | `() => void` | Function to reset the stepper. | | `activeStep` | `number` | The current active step. | -| `hasCompletedAllSteps` | `boolean` | If all steps have been completed. | | `isLastStep` | `boolean` | If the current step is the last step. | -| `isOptional` | `boolean` | If the current step is optional. | -| `currentStep` | `StepItem` | The current step object. | -| `clickable` | `boolean` | If the steps are clickable. | +| `isOptionalStep` | `boolean` | If the current step is optional. | +| `isDisabledStep` | `boolean` | If the current step is disabled. | | `isError` | `boolean` | If the stepper has an error. | | `isLoading` | `boolean` | If the stepper is loading. | | `isVertical` | `boolean` | If the stepper is vertical. | -| `stepCount` | `number` | The total number of steps. | | `expandVerticalSteps` | `boolean` | If the vertical steps should be expanded. | -| `activeStep` | `number` | The current active step. | +| `nextStep` | `() => void` | Function to go to the next step. | +| `prevStep` | `() => void` | Function to go to the previous step. | +| `setStep` | `(index: number) => void` | Function to set a specific step. | +| `resetSteps` | `() => void` | Function to reset the stepper. | +| `stepCount` | `number` | The total number of steps. | | `initialStep` | `number` | The initial active step. | +| `clickable` | `boolean` | If the steps are clickable. | +| `hasCompletedAllSteps` | `boolean` | If all steps have been completed. | +| `currentStep` | `StepItem` | The current step object. | diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index d77997ba299..1ceb1d003b0 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -20,9 +20,9 @@ export default function StepperDemo() { steps={steps} onClickStep={() => alert("You clicked on a step!")} > - {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -39,11 +39,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -54,13 +54,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx index 6ddf8af8716..2e79b0c855e 100644 --- a/apps/www/registry/default/example/stepper-custom-icons.tsx +++ b/apps/www/registry/default/example/stepper-custom-icons.tsx @@ -18,9 +18,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label, icon }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -37,11 +37,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -52,13 +52,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx index 385015fa587..3d4a7a4914f 100644 --- a/apps/www/registry/default/example/stepper-demo.tsx +++ b/apps/www/registry/default/example/stepper-demo.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, + isDisabledStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-description.tsx b/apps/www/registry/default/example/stepper-description.tsx index af57848d2a7..fcd9712cc0e 100644 --- a/apps/www/registry/default/example/stepper-description.tsx +++ b/apps/www/registry/default/example/stepper-description.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label, description }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, + isDisabledStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-footer-inside.tsx b/apps/www/registry/default/example/stepper-footer-inside.tsx index 446f8ae25b1..600ca2a44d2 100644 --- a/apps/www/registry/default/example/stepper-footer-inside.tsx +++ b/apps/www/registry/default/example/stepper-footer-inside.tsx @@ -1,10 +1,10 @@ -import { Button } from "@/registry/new-york/ui/button" +import { Button } from "@/registry/default/ui/button" import { Step, StepItem, Stepper, useStepper, -} from "@/registry/new-york/ui/stepper" +} from "@/registry/default/ui/stepper" const steps = [ { label: "Step 1" }, @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -33,12 +33,12 @@ export default function StepperDemo() { } const StepButtons = () => { - const { nextStep, prevStep, activeStep, isLastStep, isOptional } = + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = useStepper() return (
) } const FinalStep = () => { - const { hasCompletedAllSteps, reset } = useStepper() + const { hasCompletedAllSteps, resetSteps } = useStepper() if (!hasCompletedAllSteps) { return null @@ -65,7 +65,7 @@ const FinalStep = () => {

Woohoo! All steps completed! 🎉

-
diff --git a/apps/www/registry/default/example/stepper-form.tsx b/apps/www/registry/default/example/stepper-form.tsx index 871f4de2337..ae818bcb094 100644 --- a/apps/www/registry/default/example/stepper-form.tsx +++ b/apps/www/registry/default/example/stepper-form.tsx @@ -19,32 +19,24 @@ import { Step, Stepper, useStepper } from "@/registry/default/ui/stepper" import { toast } from "@/registry/default/ui/use-toast" const steps = [ - { id: 0, label: "Step 1", description: "Description 1" }, - { id: 1, label: "Step 2", description: "Description 2" }, + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, ] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map((stepProps, index) => { if (index === 0) { return ( - + ) } return ( - + ) @@ -151,25 +143,24 @@ function SecondStepForm() { function StepperFormActions() { const { - nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return (
{hasCompletedAllSteps ? ( - ) : ( <> )} @@ -186,7 +177,7 @@ function StepperFormActions() { } function MyStepperFooter() { - const { activeStep, reset, steps } = useStepper() + const { activeStep, resetSteps, steps } = useStepper() if (activeStep !== steps.length) { return null @@ -194,7 +185,7 @@ function MyStepperFooter() { return (
- +
) } diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx index 36e376940dd..3f66b3b70ca 100644 --- a/apps/www/registry/default/example/stepper-optional-steps.tsx +++ b/apps/www/registry/default/example/stepper-optional-steps.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index 715a48e52f1..d099cdce417 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-sizes.tsx b/apps/www/registry/default/example/stepper-sizes.tsx index ba388c32dd0..db77ed451a0 100644 --- a/apps/www/registry/default/example/stepper-sizes.tsx +++ b/apps/www/registry/default/example/stepper-sizes.tsx @@ -41,9 +41,9 @@ export default function StepperDemo() {
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -60,11 +60,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -75,13 +75,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-state.tsx b/apps/www/registry/default/example/stepper-state.tsx index 35b4ebfdef0..291128c270c 100644 --- a/apps/www/registry/default/example/stepper-state.tsx +++ b/apps/www/registry/default/example/stepper-state.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, isError, } = useStepper() return ( @@ -51,13 +51,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/example/stepper-variants.tsx b/apps/www/registry/default/example/stepper-variants.tsx index 19137db8d1d..385e3685228 100644 --- a/apps/www/registry/default/example/stepper-variants.tsx +++ b/apps/www/registry/default/example/stepper-variants.tsx @@ -42,9 +42,9 @@ export default function StepperDemo() {
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -61,11 +61,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -76,13 +76,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index dfa46206c7d..2e50b84cfee 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -26,7 +26,7 @@ const StepperContext = React.createContext< StepperContextValue & { nextStep: () => void prevStep: () => void - reset: () => void + resetSteps: () => void setStep: (step: number) => void } >({ @@ -35,7 +35,7 @@ const StepperContext = React.createContext< initialStep: 0, nextStep: () => {}, prevStep: () => {}, - reset: () => {}, + resetSteps: () => {}, setStep: () => {}, }) @@ -58,7 +58,7 @@ const StepperProvider = ({ value, children }: StepperContextProviderProps) => { setActiveStep((prev) => prev - 1) } - const reset = () => { + const resetSteps = () => { setActiveStep(value.initialStep) } @@ -75,7 +75,7 @@ const StepperProvider = ({ value, children }: StepperContextProviderProps) => { activeStep, nextStep, prevStep, - reset, + resetSteps, setStep, }} > @@ -99,13 +99,16 @@ function useStepper() { const hasCompletedAllSteps = context.activeStep === context.steps.length const currentStep = context.steps[context.activeStep] - const isOptional = currentStep?.optional + const isOptionalStep = currentStep?.optional + + const isDisabledStep = context.activeStep === 0 return { ...rest, isLastStep, hasCompletedAllSteps, - isOptional, + isOptionalStep, + isDisabledStep, currentStep, } } @@ -131,6 +134,7 @@ function useMediaQuery(query: string) { // <---------- STEPS ----------> type StepItem = { + id?: string label?: string description?: string icon?: IconType @@ -488,7 +492,7 @@ const VerticalStep = React.forwardRef( styles, scrollTracking, orientation, - steps + steps, } = useStepper() const opacity = hasVisited ? 1 : 0.8 @@ -721,6 +725,7 @@ const StepButtonContainer = ({ children, isError, isLoading: isLoadingProp, + onClickStep, }: StepButtonContainerProps) => { const { clickable, @@ -730,6 +735,8 @@ const StepButtonContainer = ({ size, } = useStepper() + const currentStepClickable = clickable || !!onClickStep + const isLoading = isLoadingProp || isLoadingContext if (variant === "simple") { @@ -741,8 +748,9 @@ const StepButtonContainer = ({ variant="ghost" className={cn( "stepper__step-button-container", - "rounded-full p-0", + "rounded-full p-0 pointer-events-none", "border-2 flex rounded-full justify-center items-center", + "data-[clickable=true]:pointer-events-auto", "data-[active=true]:bg-blue-500 data-[active=true]:border-blue-500 data-[active=true]:text-primary-foreground dark:data-[active=true]:text-primary", "data-[current=true]:border-blue-500 data-[current=true]:bg-secondary", "data-[invalid=true]:!bg-destructive data-[invalid=true]:!border-destructive data-[invalid=true]:!text-primary-foreground dark:data-[invalid=true]:!text-primary", @@ -753,7 +761,7 @@ const StepButtonContainer = ({ data-current={isCurrentStep} data-invalid={isError && (isCurrentStep || isCompletedStep)} data-active={isCompletedStep} - data-clickable={clickable} + data-clickable={currentStepClickable} data-loading={isLoading && (isCurrentStep || isCompletedStep)} > {children} diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx index 4a2d7384752..75cc3ecbeac 100644 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -20,9 +20,9 @@ export default function StepperDemo() { steps={steps} onClickStep={() => alert("You clicked on a step!")} > - {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -39,11 +39,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -54,13 +54,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx index 1d42663bd55..006ca9759e7 100644 --- a/apps/www/registry/new-york/example/stepper-custom-icons.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-icons.tsx @@ -18,9 +18,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label, icon }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -37,11 +37,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -52,13 +52,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index b6f6ff91b17..3bd8839b4d2 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, + isDisabledStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-description.tsx b/apps/www/registry/new-york/example/stepper-description.tsx index f45ee509480..3e51c7ab266 100644 --- a/apps/www/registry/new-york/example/stepper-description.tsx +++ b/apps/www/registry/new-york/example/stepper-description.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label, description }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, + isDisabledStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-footer-inside.tsx b/apps/www/registry/new-york/example/stepper-footer-inside.tsx index 446f8ae25b1..073a063121d 100644 --- a/apps/www/registry/new-york/example/stepper-footer-inside.tsx +++ b/apps/www/registry/new-york/example/stepper-footer-inside.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -33,12 +33,12 @@ export default function StepperDemo() { } const StepButtons = () => { - const { nextStep, prevStep, activeStep, isLastStep, isOptional } = + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = useStepper() return (
) } const FinalStep = () => { - const { hasCompletedAllSteps, reset } = useStepper() + const { hasCompletedAllSteps, resetSteps } = useStepper() if (!hasCompletedAllSteps) { return null @@ -65,7 +65,7 @@ const FinalStep = () => {

Woohoo! All steps completed! 🎉

-
diff --git a/apps/www/registry/new-york/example/stepper-form.tsx b/apps/www/registry/new-york/example/stepper-form.tsx index d69c35bd4bf..0e6ed49a292 100644 --- a/apps/www/registry/new-york/example/stepper-form.tsx +++ b/apps/www/registry/new-york/example/stepper-form.tsx @@ -19,32 +19,24 @@ import { Step, Stepper, useStepper } from "@/registry/new-york/ui/stepper" import { toast } from "@/registry/new-york/ui/use-toast" const steps = [ - { id: 0, label: "Step 1", description: "Description 1" }, - { id: 1, label: "Step 2", description: "Description 2" }, + { label: "Step 1", description: "Description 1" }, + { label: "Step 2", description: "Description 2" }, ] export default function StepperDemo() { return (
- {steps.map((step, index) => { + {steps.map((stepProps, index) => { if (index === 0) { return ( - + ) } return ( - + ) @@ -151,25 +143,24 @@ function SecondStepForm() { function StepperFormActions() { const { - nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return (
{hasCompletedAllSteps ? ( - ) : ( <> )} @@ -186,7 +177,7 @@ function StepperFormActions() { } function MyStepperFooter() { - const { activeStep, reset, steps } = useStepper() + const { activeStep, resetSteps, steps } = useStepper() if (activeStep !== steps.length) { return null @@ -194,7 +185,7 @@ function MyStepperFooter() { return (
- +
) } diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx index d2965006a7d..53c3bfdcc48 100644 --- a/apps/www/registry/new-york/example/stepper-optional-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-optional-steps.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index 2ed096e9bd1..7959483bacc 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -50,13 +50,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-sizes.tsx b/apps/www/registry/new-york/example/stepper-sizes.tsx index e392fb4a7ed..672cb00897d 100644 --- a/apps/www/registry/new-york/example/stepper-sizes.tsx +++ b/apps/www/registry/new-york/example/stepper-sizes.tsx @@ -41,9 +41,9 @@ export default function StepperDemo() {
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -60,11 +60,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -75,13 +75,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-state.tsx b/apps/www/registry/new-york/example/stepper-state.tsx index db4599209c6..d86117d9e13 100644 --- a/apps/www/registry/new-york/example/stepper-state.tsx +++ b/apps/www/registry/new-york/example/stepper-state.tsx @@ -16,9 +16,9 @@ export default function StepperDemo() { return (
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -35,11 +35,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, isError, } = useStepper() return ( @@ -51,13 +51,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/example/stepper-variants.tsx b/apps/www/registry/new-york/example/stepper-variants.tsx index 7828ca3920b..a7fc0494a62 100644 --- a/apps/www/registry/new-york/example/stepper-variants.tsx +++ b/apps/www/registry/new-york/example/stepper-variants.tsx @@ -42,9 +42,9 @@ export default function StepperDemo() {
- {steps.map(({ label }, index) => { + {steps.map((stepProps, index) => { return ( - +

Step {index + 1}

@@ -61,11 +61,11 @@ const Footer = () => { const { nextStep, prevStep, - reset, - activeStep, + resetSteps, + isDisabledStep, hasCompletedAllSteps, isLastStep, - isOptional, + isOptionalStep, } = useStepper() return ( <> @@ -76,13 +76,13 @@ const Footer = () => { )}
{hasCompletedAllSteps ? ( - ) : ( <> )} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index ecda712f4e7..c9ae70d1243 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -26,7 +26,7 @@ const StepperContext = React.createContext< StepperContextValue & { nextStep: () => void prevStep: () => void - reset: () => void + resetSteps: () => void setStep: (step: number) => void } >({ @@ -35,7 +35,7 @@ const StepperContext = React.createContext< initialStep: 0, nextStep: () => {}, prevStep: () => {}, - reset: () => {}, + resetSteps: () => {}, setStep: () => {}, }) @@ -58,7 +58,7 @@ const StepperProvider = ({ value, children }: StepperContextProviderProps) => { setActiveStep((prev) => prev - 1) } - const reset = () => { + const resetSteps = () => { setActiveStep(value.initialStep) } @@ -75,7 +75,7 @@ const StepperProvider = ({ value, children }: StepperContextProviderProps) => { activeStep, nextStep, prevStep, - reset, + resetSteps, setStep, }} > @@ -99,13 +99,16 @@ function useStepper() { const hasCompletedAllSteps = context.activeStep === context.steps.length const currentStep = context.steps[context.activeStep] - const isOptional = currentStep?.optional + const isOptionalStep = currentStep?.optional + + const isDisabledStep = context.activeStep === 0 return { ...rest, isLastStep, hasCompletedAllSteps, - isOptional, + isOptionalStep, + isDisabledStep, currentStep, } } @@ -131,6 +134,7 @@ function useMediaQuery(query: string) { // <---------- STEPS ----------> type StepItem = { + id?: string label?: string description?: string icon?: IconType @@ -715,6 +719,7 @@ const StepButtonContainer = ({ children, isError, isLoading: isLoadingProp, + onClickStep, }: StepButtonContainerProps) => { const { clickable, @@ -724,6 +729,8 @@ const StepButtonContainer = ({ size, } = useStepper() + const currentStepClickable = clickable || !!onClickStep + const isLoading = isLoadingProp || isLoadingContext if (variant === "simple") { @@ -735,8 +742,9 @@ const StepButtonContainer = ({ variant="ghost" className={cn( "stepper__step-button-container", - "rounded-full p-0", + "rounded-full p-0 pointer-events-none", "border-2 flex rounded-full justify-center items-center", + "data-[clickable=true]:pointer-events-auto", "data-[active=true]:bg-blue-500 data-[active=true]:border-blue-500 data-[active=true]:text-primary-foreground dark:data-[active=true]:text-primary", "data-[current=true]:border-blue-500 data-[current=true]:bg-secondary", "data-[invalid=true]:!bg-destructive data-[invalid=true]:!border-destructive data-[invalid=true]:!text-primary-foreground dark:data-[invalid=true]:!text-primary", @@ -747,7 +755,7 @@ const StepButtonContainer = ({ data-current={isCurrentStep} data-invalid={isError && (isCurrentStep || isCompletedStep)} data-active={isCompletedStep} - data-clickable={clickable} + data-clickable={currentStepClickable} data-loading={isLoading && (isCurrentStep || isCompletedStep)} > {children} From e2d5312285310f301c7461a295f14e62b4971414 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 25 Mar 2024 10:38:40 -0300 Subject: [PATCH 26/62] feat: add props table component --- apps/www/components/mdx-components.tsx | 2 + apps/www/components/props-table.tsx | 148 ++++++++++ apps/www/content/docs/components/stepper.mdx | 280 +++++++++++++++---- 3 files changed, 381 insertions(+), 49 deletions(-) create mode 100644 apps/www/components/props-table.tsx diff --git a/apps/www/components/mdx-components.tsx b/apps/www/components/mdx-components.tsx index 72d659b3edb..99fd87ad1f3 100644 --- a/apps/www/components/mdx-components.tsx +++ b/apps/www/components/mdx-components.tsx @@ -11,6 +11,7 @@ import { Event } from "@/lib/events" import { cn } from "@/lib/utils" import { useConfig } from "@/hooks/use-config" import { Callout } from "@/components/callout" +import { PropsTable } from "@/components/props-table" import { CodeBlockWrapper } from "@/components/code-block-wrapper" import { ComponentExample } from "@/components/component-example" import { ComponentPreview } from "@/components/component-preview" @@ -231,6 +232,7 @@ const components = { ), Image, Callout, + PropsTable, ComponentPreview, ComponentExample, ComponentSource, diff --git a/apps/www/components/props-table.tsx b/apps/www/components/props-table.tsx new file mode 100644 index 00000000000..927d5048d90 --- /dev/null +++ b/apps/www/components/props-table.tsx @@ -0,0 +1,148 @@ +import React from "react" +import { DividerHorizontalIcon, InfoCircledIcon } from "@radix-ui/react-icons" + +import { Button } from "@/registry/new-york/ui/button" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/registry/new-york/ui/popover" +import { ScrollArea } from "@/registry/new-york/ui/scroll-area" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/registry/new-york/ui/table" + +export type PropDef = { + name: string + required?: boolean + default?: string | boolean + type?: string + typeSimple: string + description?: string | React.ReactNode +} + +export function PropsTable({ + data, + propHeaderFixedWidth = true, +}: { + data: PropDef[] + propHeaderFixedWidth?: boolean +}) { + return ( +
+ + + + + Prop + + Type + Default + + + + {data.map( + ( + { + name, + type, + typeSimple, + required, + default: defaultValue, + description, + }, + i + ) => { + return ( + + +
+ + {name} + {required ? "*" : null} + + {description && ( + + + + + { + event.preventDefault() + ;(event.currentTarget as HTMLElement)?.focus() + }} + > +

{description}

+
+
+ )} +
+
+ +
+ {Boolean(typeSimple) ? typeSimple : type} + {Boolean(typeSimple) && Boolean(type) && ( + + + + + + +
+ + {type} + +
+
+
+
+ )} +
+
+ + + {Boolean(defaultValue) ? ( + {defaultValue} + ) : ( + + )} + +
+ ) + } + )} +
+
+
+ ) +} diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index f45b5a4be67..b8e9a50ccf9 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -273,57 +273,239 @@ In some cases you may want to customize the styles of a step based on its state. ### Stepper -| Prop | Type | Default | Description | -| --------------------- | ---------------------------------------- | ------------ | --------------------------------------------------------------------- | -| `initialStep*` | `number` | `-` | Currently active step | -| `steps*` | `StepItem[]` | `-` | An array of objects with the label and description of each step. | -| `orientation` | `"horizontal" \| "vertical"` | `horizontal` | The orientation of the stepper. | -| `size` | `"sm" \| "md" \| "lg"` | `md` | The size of the stepper. | -| `state` | `"loading" \| "error"` | `-` | The state of the stepper (global). | -| `icon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed in the step button. | -| `checkIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step is completed. | -| `errorIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step has an error. | -| `responsive` | `boolean` | `true` | If the stepper should be responsive. | -| `mobileBreakpoint` | `number` | `768px` | The breakpoint at which the stepper switches to vertical orientation. | -| `scrollTracking` | `boolean` | `false` | Scroll to the active step when scrolling forward or backward. | -| `styles` | `{ [key: string]: string }` | `-` | Custom styles for the stepper. | -| `onClickStep` | `(index: number) => void` | `-` | The function to be executed when a step is clicked. | -| `variant` | `"circles" \| "circles-alt" \| "simple"` | `circles` | The design variant of the stepper. | -| `expandVerticalSteps` | `boolean` | `false` | Control whether or not the vertical steps should collapse. | - +', + description: 'The custom icon to be displayed in the step button.', + }, + { + name: 'checkIcon', + type: 'LucideIcon | React.ComponentType', + description: 'The custom icon to be displayed when the step is completed.', + }, + { + name: 'errorIcon', + type: 'LucideIcon | React.ComponentType', + description: 'The custom icon to be displayed when the step has an error.', + }, + { + name: 'responsive', + type: 'boolean', + default: 'true', + description: 'If the stepper should be responsive.', + }, + { + name: 'mobileBreakpoint', + type: 'number', + default: '768px', + description: 'The breakpoint at which the stepper switches to vertical orientation.', + }, + { + name: 'scrollTracking', + type: 'boolean', + default: 'false', + description: 'Scroll to the active step when scrolling forward or backward.', + }, + { + name: 'styles', + type: '{ [key: string]: string }', + description: 'Custom styles for the stepper.', + }, + { + name: 'onClickStep', + type: '(index: number) => void', + description: 'The function to be executed when a step is clicked.', + }, + { + name: 'variant', + type: '"circles" | "circles-alt" | "simple"', + default: 'circles', + description: 'The design variant of the stepper.', + }, + { + name: 'expandVerticalSteps', + type: 'boolean', + default: 'false', + description: 'Control whether or not the vertical steps should collapse.', + }, + ]} +/> ### Step -| Prop | Type | Default | Description | -| ----------------- | ---------------------------------------- | ------- | ---------------------------------------------------------------------- | -| `id` | `string` | `-` | The id of the step. | -| `label` | `string` | `-` | The label of the step. | -| `description` | `string` | `-` | The description of the step. | -| `optional` | `boolean` | `-` | If the step is optional. | -| `icon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed in the step button. | -| `state` | `"loading" \| "error"` | `-` | The state of the step (local). | -| `isCompletedStep` | `boolean` | `-` | Individually control each step state, defaults to active step | -| `isKeepError` | `boolean` | `-` | Individually control if each step should keep showing the error state. | -| `checkIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step is completed. | -| `errorIcon` | `LucideIcon \| React.ComponentType` | `-` | The custom icon to be displayed when the step has an error. | +', + description: 'The custom icon to be displayed in the step button.', + }, + { + name: 'state', + type: '"loading" | "error"', + description: 'The state of the step (local).', + }, + { + name: 'isCompletedStep', + type: 'boolean', + description: 'Individually control each step state, defaults to active step.', + }, + { + name: 'isKeepError', + type: 'boolean', + description: 'Individually control if each step should keep showing the error state.', + }, + { + name: 'checkIcon', + type: 'LucideIcon | React.ComponentType', + description: 'The custom icon to be displayed when the step is completed.', + }, + { + name: 'errorIcon', + type: 'LucideIcon | React.ComponentType', + description: 'The custom icon to be displayed when the step has an error.', + }, + ]} +/> ### useStepper -| Prop | Type | Description | -| ---------------------- | ------------------------- | ----------------------------------------- | -| `activeStep` | `number` | The current active step. | -| `isLastStep` | `boolean` | If the current step is the last step. | -| `isOptionalStep` | `boolean` | If the current step is optional. | -| `isDisabledStep` | `boolean` | If the current step is disabled. | -| `isError` | `boolean` | If the stepper has an error. | -| `isLoading` | `boolean` | If the stepper is loading. | -| `isVertical` | `boolean` | If the stepper is vertical. | -| `expandVerticalSteps` | `boolean` | If the vertical steps should be expanded. | -| `nextStep` | `() => void` | Function to go to the next step. | -| `prevStep` | `() => void` | Function to go to the previous step. | -| `setStep` | `(index: number) => void` | Function to set a specific step. | -| `resetSteps` | `() => void` | Function to reset the stepper. | -| `stepCount` | `number` | The total number of steps. | -| `initialStep` | `number` | The initial active step. | -| `clickable` | `boolean` | If the steps are clickable. | -| `hasCompletedAllSteps` | `boolean` | If all steps have been completed. | -| `currentStep` | `StepItem` | The current step object. | + void', + description: 'Function to go to the next step.', + }, + { + name: 'prevStep', + type: '() => void', + description: 'Function to go to the previous step.', + }, + { + name: 'setStep', + type: '(index: number) => void', + description: 'Function to set a specific step.', + }, + { + name: 'resetSteps', + type: '() => void', + description: 'Function to reset the stepper.', + }, + { + name: 'stepCount', + type: 'number', + description: 'The total number of steps.', + }, + { + name: 'initialStep', + type: 'number', + description: 'The initial active step.', + }, + { + name: 'clickable', + type: 'boolean', + description: 'If the steps are clickable.', + }, + { + name: 'hasCompletedAllSteps', + type: 'boolean', + description: 'If all steps have been completed.', + }, + { + name: 'currentStep', + type: 'StepItem', + description: 'The current step object.', + }, + ]} +/> \ No newline at end of file From a1023bc52e88db2acf2f95f4cacc810c1e374173 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 25 Mar 2024 10:38:52 -0300 Subject: [PATCH 27/62] feat: add props table component --- apps/www/components/mdx-components.tsx | 2 +- apps/www/components/props-table.tsx | 8 +- apps/www/content/docs/components/stepper.mdx | 267 ++++++++++--------- 3 files changed, 144 insertions(+), 133 deletions(-) diff --git a/apps/www/components/mdx-components.tsx b/apps/www/components/mdx-components.tsx index 99fd87ad1f3..a6aba5a7df8 100644 --- a/apps/www/components/mdx-components.tsx +++ b/apps/www/components/mdx-components.tsx @@ -11,13 +11,13 @@ import { Event } from "@/lib/events" import { cn } from "@/lib/utils" import { useConfig } from "@/hooks/use-config" import { Callout } from "@/components/callout" -import { PropsTable } from "@/components/props-table" import { CodeBlockWrapper } from "@/components/code-block-wrapper" import { ComponentExample } from "@/components/component-example" import { ComponentPreview } from "@/components/component-preview" import { ComponentSource } from "@/components/component-source" import { CopyButton, CopyNpmCommandButton } from "@/components/copy-button" import { FrameworkDocs } from "@/components/framework-docs" +import { PropsTable } from "@/components/props-table" import { StyleWrapper } from "@/components/style-wrapper" import { Accordion, diff --git a/apps/www/components/props-table.tsx b/apps/www/components/props-table.tsx index 927d5048d90..91c2dbf868e 100644 --- a/apps/www/components/props-table.tsx +++ b/apps/www/components/props-table.tsx @@ -34,8 +34,8 @@ export function PropsTable({ propHeaderFixedWidth?: boolean }) { return ( -
- +
+
@@ -94,7 +94,9 @@ export function PropsTable({
- {Boolean(typeSimple) ? typeSimple : type} + + {Boolean(typeSimple) ? typeSimple : type} + {Boolean(typeSimple) && Boolean(type) && ( diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index b8e9a50ccf9..37d5c8a4848 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -276,88 +276,93 @@ In some cases you may want to customize the styles of a step based on its state. ', - description: 'The custom icon to be displayed in the step button.', + name: "icon", + type: "LucideIcon | React.ComponentType", + description: "The custom icon to be displayed in the step button.", }, { - name: 'checkIcon', - type: 'LucideIcon | React.ComponentType', - description: 'The custom icon to be displayed when the step is completed.', + name: "checkIcon", + type: "LucideIcon | React.ComponentType", + description: + "The custom icon to be displayed when the step is completed.", }, { - name: 'errorIcon', - type: 'LucideIcon | React.ComponentType', - description: 'The custom icon to be displayed when the step has an error.', + name: "errorIcon", + type: "LucideIcon | React.ComponentType", + description: + "The custom icon to be displayed when the step has an error.", }, { - name: 'responsive', - type: 'boolean', - default: 'true', - description: 'If the stepper should be responsive.', + name: "responsive", + type: "boolean", + default: "true", + description: "If the stepper should be responsive.", }, { - name: 'mobileBreakpoint', - type: 'number', - default: '768px', - description: 'The breakpoint at which the stepper switches to vertical orientation.', + name: "mobileBreakpoint", + type: "number", + default: "768px", + description: + "The breakpoint at which the stepper switches to vertical orientation.", }, { - name: 'scrollTracking', - type: 'boolean', - default: 'false', - description: 'Scroll to the active step when scrolling forward or backward.', + name: "scrollTracking", + type: "boolean", + default: "false", + description: + "Scroll to the active step when scrolling forward or backward.", }, { - name: 'styles', - type: '{ [key: string]: string }', - description: 'Custom styles for the stepper.', + name: "styles", + type: "{ [key: string]: string }", + description: "Custom styles for the stepper.", }, { - name: 'onClickStep', - type: '(index: number) => void', - description: 'The function to be executed when a step is clicked.', + name: "onClickStep", + type: "(index: number) => void", + description: "The function to be executed when a step is clicked.", }, { - name: 'variant', + name: "variant", type: '"circles" | "circles-alt" | "simple"', - default: 'circles', - description: 'The design variant of the stepper.', + default: "circles", + description: "The design variant of the stepper.", }, { - name: 'expandVerticalSteps', - type: 'boolean', - default: 'false', - description: 'Control whether or not the vertical steps should collapse.', + name: "expandVerticalSteps", + type: "boolean", + default: "false", + description: "Control whether or not the vertical steps should collapse.", }, ]} /> @@ -366,54 +371,58 @@ In some cases you may want to customize the styles of a step based on its state. ', - description: 'The custom icon to be displayed in the step button.', + name: "icon", + type: "LucideIcon | React.ComponentType", + description: "The custom icon to be displayed in the step button.", }, { - name: 'state', + name: "state", type: '"loading" | "error"', - description: 'The state of the step (local).', + description: "The state of the step (local).", }, { - name: 'isCompletedStep', - type: 'boolean', - description: 'Individually control each step state, defaults to active step.', + name: "isCompletedStep", + type: "boolean", + description: + "Individually control each step state, defaults to active step.", }, { - name: 'isKeepError', - type: 'boolean', - description: 'Individually control if each step should keep showing the error state.', + name: "isKeepError", + type: "boolean", + description: + "Individually control if each step should keep showing the error state.", }, { - name: 'checkIcon', - type: 'LucideIcon | React.ComponentType', - description: 'The custom icon to be displayed when the step is completed.', + name: "checkIcon", + type: "LucideIcon | React.ComponentType", + description: + "The custom icon to be displayed when the step is completed.", }, { - name: 'errorIcon', - type: 'LucideIcon | React.ComponentType', - description: 'The custom icon to be displayed when the step has an error.', + name: "errorIcon", + type: "LucideIcon | React.ComponentType", + description: + "The custom icon to be displayed when the step has an error.", }, ]} /> @@ -423,89 +432,89 @@ In some cases you may want to customize the styles of a step based on its state. void', - description: 'Function to go to the next step.', + name: "nextStep", + type: "() => void", + description: "Function to go to the next step.", }, { - name: 'prevStep', - type: '() => void', - description: 'Function to go to the previous step.', + name: "prevStep", + type: "() => void", + description: "Function to go to the previous step.", }, { - name: 'setStep', - type: '(index: number) => void', - description: 'Function to set a specific step.', + name: "setStep", + type: "(index: number) => void", + description: "Function to set a specific step.", }, { - name: 'resetSteps', - type: '() => void', - description: 'Function to reset the stepper.', + name: "resetSteps", + type: "() => void", + description: "Function to reset the stepper.", }, { - name: 'stepCount', - type: 'number', - description: 'The total number of steps.', + name: "stepCount", + type: "number", + description: "The total number of steps.", }, { - name: 'initialStep', - type: 'number', - description: 'The initial active step.', + name: "initialStep", + type: "number", + description: "The initial active step.", }, { - name: 'clickable', - type: 'boolean', - description: 'If the steps are clickable.', + name: "clickable", + type: "boolean", + description: "If the steps are clickable.", }, { - name: 'hasCompletedAllSteps', - type: 'boolean', - description: 'If all steps have been completed.', + name: "hasCompletedAllSteps", + type: "boolean", + description: "If all steps have been completed.", }, { - name: 'currentStep', - type: 'StepItem', - description: 'The current step object.', + name: "currentStep", + type: "StepItem", + description: "The current step object.", }, ]} -/> \ No newline at end of file +/> From 1be54affd3bfe7dc48636a64dea0cc5cfdbbba0c Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 25 Mar 2024 10:44:48 -0300 Subject: [PATCH 28/62] chore: update hook docs --- apps/www/content/docs/components/stepper.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 37d5c8a4848..8ca846a7137 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -429,6 +429,24 @@ In some cases you may want to customize the styles of a step based on its state. ### useStepper +In order to use the hook, we simply have to import it and use it inside the `` component as a wrapper. + +```tsx +import { useStepper } from "@/components/ui/stepper" + +export funcion UseStepperDemo() { + { ... } = useStepper(); + + return ( +
+ { ... } +
+ ) +} +``` + +The values returned by the hook are the following: + Date: Mon, 25 Mar 2024 11:14:35 -0300 Subject: [PATCH 29/62] chore: update clickable example --- .../registry/default/example/stepper-clickable-steps.tsx | 9 ++++++++- .../new-york/example/stepper-clickable-steps.tsx | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index 1ceb1d003b0..3354ebd9882 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -5,6 +5,7 @@ import { Stepper, useStepper, } from "@/registry/default/ui/stepper" +import { toast } from "@/registry/default/ui/use-toast" const steps = [ { label: "Step 1" }, @@ -18,7 +19,13 @@ export default function StepperDemo() { alert("You clicked on a step!")} + onClickStep={() => + toast({ + title: "Step clicked", + description: + "This event is executed globally for all steps. If you want to have an event for a specific step, use the `onClickStep` prop of the independent step.", + }) + } > {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx index 75cc3ecbeac..7511b6744d6 100644 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -5,6 +5,7 @@ import { Stepper, useStepper, } from "@/registry/new-york/ui/stepper" +import { toast } from "@/registry/new-york/ui/use-toast" const steps = [ { label: "Step 1" }, @@ -18,7 +19,13 @@ export default function StepperDemo() { alert("You clicked on a step!")} + onClickStep={() => + toast({ + title: "Step clicked", + description: + "This event is executed globally for all steps. If you want to have an event for a specific step, use the `onClickStep` prop of the independent step.", + }) + } > {steps.map((stepProps, index) => { return ( From 30c045fc2d093d2161a8cd12ab19570422c99691 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 27 Mar 2024 11:46:53 -0300 Subject: [PATCH 30/62] chore: remove unnecessary optional --- apps/www/registry/default/ui/stepper.tsx | 2 +- apps/www/registry/new-york/ui/stepper.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 2e50b84cfee..0cd6afc659c 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -99,7 +99,7 @@ function useStepper() { const hasCompletedAllSteps = context.activeStep === context.steps.length const currentStep = context.steps[context.activeStep] - const isOptionalStep = currentStep?.optional + const isOptionalStep = !!currentStep.optional const isDisabledStep = context.activeStep === 0 diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index c9ae70d1243..615ba89a887 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -99,7 +99,7 @@ function useStepper() { const hasCompletedAllSteps = context.activeStep === context.steps.length const currentStep = context.steps[context.activeStep] - const isOptionalStep = currentStep?.optional + const isOptionalStep = !!currentStep.optional const isDisabledStep = context.activeStep === 0 From 2ed9fdd3cd11d2c14080c75d2423c6ac877ce561 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 27 Mar 2024 12:02:48 -0300 Subject: [PATCH 31/62] chore: convert to boolean the value of isOptionalStep --- apps/www/registry/default/ui/stepper.tsx | 2 +- apps/www/registry/new-york/ui/stepper.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 0cd6afc659c..ce67b663116 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -99,7 +99,7 @@ function useStepper() { const hasCompletedAllSteps = context.activeStep === context.steps.length const currentStep = context.steps[context.activeStep] - const isOptionalStep = !!currentStep.optional + const isOptionalStep = !!currentStep?.optional const isDisabledStep = context.activeStep === 0 diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 615ba89a887..6893b3d3b7b 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -99,7 +99,7 @@ function useStepper() { const hasCompletedAllSteps = context.activeStep === context.steps.length const currentStep = context.steps[context.activeStep] - const isOptionalStep = !!currentStep.optional + const isOptionalStep = !!currentStep?.optional const isDisabledStep = context.activeStep === 0 From fc77b631985f502d01a409e9b81b9789cb9ef300 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 31 Mar 2024 10:24:28 -0300 Subject: [PATCH 32/62] fix: tailwind classes order --- apps/www/components/props-table.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/www/components/props-table.tsx b/apps/www/components/props-table.tsx index 91c2dbf868e..c1783b83cda 100644 --- a/apps/www/components/props-table.tsx +++ b/apps/www/components/props-table.tsx @@ -34,7 +34,7 @@ export function PropsTable({ propHeaderFixedWidth?: boolean }) { return ( -
+
@@ -62,7 +62,7 @@ export function PropsTable({
- + {name} {required ? "*" : null} @@ -71,7 +71,7 @@ export function PropsTable({ @@ -94,7 +94,7 @@ export function PropsTable({
- + {Boolean(typeSimple) ? typeSimple : type} {Boolean(typeSimple) && Boolean(type) && ( @@ -102,7 +102,7 @@ export function PropsTable({ @@ -132,7 +132,7 @@ export function PropsTable({ {Boolean(defaultValue) ? ( - {defaultValue} + {defaultValue} ) : ( Date: Sun, 31 Mar 2024 10:24:52 -0300 Subject: [PATCH 33/62] chore: add use client --- apps/www/registry/default/ui/stepper.tsx | 2 ++ apps/www/registry/new-york/ui/stepper.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index ce67b663116..9ab8451b4b8 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -1,3 +1,5 @@ +"use client" + import * as React from "react" import { cva } from "class-variance-authority" import { CheckIcon, Loader2, LucideIcon, X } from "lucide-react" diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 6893b3d3b7b..7f32f6d8ebb 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -1,3 +1,5 @@ +"use client" + import * as React from "react" import { cva } from "class-variance-authority" import { CheckIcon, Loader2, LucideIcon, X } from "lucide-react" From b20545b578d0717b297dbe15d2d6618b482b2ac9 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 31 Mar 2024 10:27:51 -0300 Subject: [PATCH 34/62] chore: update registry --- apps/www/public/registry/styles/default/stepper.json | 2 +- apps/www/public/registry/styles/new-york/stepper.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index 2738a058f75..2454490bd50 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "import * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n reset: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n reset: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const reset = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptional = currentStep?.optional\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptional,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n responsiveBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"2rem\",\n md: \"2.25rem\",\n lg: \"2.5rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n responsiveBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${responsiveBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"2rem\",\n md: \"2.25rem\",\n lg: \"2.5rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index c033347785d..31f10c5bbbc 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "import * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n reset: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n reset: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const reset = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptional = currentStep?.optional\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptional,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n responsiveBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"1.75rem\",\n md: \"2rem\",\n lg: \"2.25rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n responsiveBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${responsiveBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"1.75rem\",\n md: \"2rem\",\n lg: \"2.25rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" From e8eaa848e39555d256b5e1b5b1e14309b3ce2ee4 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 31 Mar 2024 19:01:16 -0300 Subject: [PATCH 35/62] chore: remove render horizontal function --- apps/www/registry/default/ui/stepper.tsx | 21 +++++++++++---------- apps/www/registry/new-york/ui/stepper.tsx | 21 +++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 9ab8451b4b8..0698a191e15 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -333,19 +333,20 @@ const HorizontalContent = ({ children }: { children: React.ReactNode }) => { const { activeStep } = useStepper() const childArr = React.Children.toArray(children) - const renderHorizontalContent = () => { - if (activeStep <= childArr.length) { - return React.Children.map(childArr[activeStep], (node) => { - if (!React.isValidElement(node)) { - return - } - return React.Children.map(node.props.children, (childNode) => childNode) - }) - } + if (activeStep > childArr.length) { return null } - return <>{renderHorizontalContent()} + return ( + <> + {React.Children.map(childArr[activeStep], (node) => { + if (!React.isValidElement(node)) { + return null + } + return React.Children.map(node.props.children, (childNode) => childNode) + })} + + ) } // <---------- STEP ----------> diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 7f32f6d8ebb..4dc77262e6c 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -333,19 +333,20 @@ const HorizontalContent = ({ children }: { children: React.ReactNode }) => { const { activeStep } = useStepper() const childArr = React.Children.toArray(children) - const renderHorizontalContent = () => { - if (activeStep <= childArr.length) { - return React.Children.map(childArr[activeStep], (node) => { - if (!React.isValidElement(node)) { - return - } - return React.Children.map(node.props.children, (childNode) => childNode) - }) - } + if (activeStep > childArr.length) { return null } - return <>{renderHorizontalContent()} + return ( + <> + {React.Children.map(childArr[activeStep], (node) => { + if (!React.isValidElement(node)) { + return null + } + return React.Children.map(node.props.children, (childNode) => childNode) + })} + + ) } // <---------- STEP ----------> From cee4e37f968c036c40c480ee031f4ecebe64c686 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Tue, 2 Apr 2024 00:12:09 -0300 Subject: [PATCH 36/62] chore: update registry --- apps/www/public/registry/styles/default/stepper.json | 2 +- apps/www/public/registry/styles/new-york/stepper.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index 2454490bd50..286a5e84be1 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"2rem\",\n md: \"2.25rem\",\n lg: \"2.5rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"2rem\",\n md: \"2.25rem\",\n lg: \"2.5rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index 31f10c5bbbc..6620d396ae6 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"1.75rem\",\n md: \"2rem\",\n lg: \"2.25rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n const renderHorizontalContent = () => {\n if (activeStep <= childArr.length) {\n return React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })\n }\n return null\n }\n\n return <>{renderHorizontalContent()}\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"1.75rem\",\n md: \"2rem\",\n lg: \"2.25rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" From 04c66dc284b7fab642a292a0ef69bfedf9e13baf Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Thu, 4 Apr 2024 00:26:26 -0300 Subject: [PATCH 37/62] chore: improve examples and add css variables prop --- apps/www/content/docs/components/stepper.mdx | 21 +++++-- .../registry/styles/default/stepper.json | 2 +- .../registry/styles/new-york/stepper.json | 2 +- .../registry/default/example/stepper-form.tsx | 2 +- .../default/example/stepper-orientation.tsx | 26 +++++++- .../default/example/stepper-variants.tsx | 14 ++--- apps/www/registry/default/ui/stepper.tsx | 63 ++++++++++--------- .../new-york/example/stepper-form.tsx | 2 +- .../new-york/example/stepper-orientation.tsx | 26 +++++++- .../new-york/example/stepper-variants.tsx | 14 ++--- apps/www/registry/new-york/ui/stepper.tsx | 63 ++++++++++--------- 11 files changed, 154 insertions(+), 81 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 8ca846a7137..5da06b257df 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -182,9 +182,9 @@ In this example, the second step is optional. You can visualize the change of th There are 3 design variants for the Stepper component: -- `circles`: allows you to display each step in a circular shape. The label and description are positioned horizontally next to the button of each step. -- `circles-alt`: same circular design as the circles variant but the label and description are positioned vertically below the button of each step. -- `simple`: this variant features a line layout with the title and description positioned below the line. +- `circle`: allows you to display each step in a circular shape. The label and description are positioned horizontally next to the button of each step. +- `circle-alt`: same circular design as the circle variant but the label and description are positioned vertically below the button of each step. +- `line`: this variant features a line layout with the title and description positioned below the line. @@ -269,6 +269,11 @@ In some cases you may want to customize the styles of a step based on its state. - `data-completed`: The step that is completed - `data-optional`: The step that is optional +Finally, we also have the `variables` prop that allows you to set values for the css variables that calculate the position of the separator lines. These variables can be useful when we need to set custom elements that have a different size than those offered by the component. + +- `--step-icon-size`: defines the width of the step icon. It is important to define this value in pixels. By default it has the values of the width of a shadcn/ui button depending if the style is default (`36px, 40px, 44px`) or new york (`32px, 36px, 40px`). +- `--step-gap`: defines the gap between the separator and the elements that follow it. The default value is `8px`. + ## API ### Stepper @@ -347,6 +352,11 @@ In some cases you may want to customize the styles of a step based on its state. type: "{ [key: string]: string }", description: "Custom styles for the stepper.", }, + { + name: "variables", + type: "{ [key: string]: string }", + description: "Custom css variables values for the stepper.", + }, { name: "onClickStep", type: "(index: number) => void", @@ -354,8 +364,8 @@ In some cases you may want to customize the styles of a step based on its state. }, { name: "variant", - type: '"circles" | "circles-alt" | "simple"', - default: "circles", + type: '"circle" | "circle-alt" | "line"', + default: "circle", description: "The design variant of the stepper.", }, { @@ -366,6 +376,7 @@ In some cases you may want to customize the styles of a step based on its state. }, ]} /> + ### Step \n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"2rem\",\n md: \"2.25rem\",\n lg: \"2.5rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index 6620d396ae6..0527684d502 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circles\" | \"circles-alt\" | \"simple\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"1.75rem\",\n md: \"2rem\",\n lg: \"2.25rem\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circles: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n simple: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"simple\" && \"ps-[--step-icon-size]\",\n variant === \"simple\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"simple\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"simple\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/stepper-form.tsx b/apps/www/registry/default/example/stepper-form.tsx index ae818bcb094..725ccbb19c5 100644 --- a/apps/www/registry/default/example/stepper-form.tsx +++ b/apps/www/registry/default/example/stepper-form.tsx @@ -26,7 +26,7 @@ const steps = [ export default function StepperDemo() { return (
- + {steps.map((stepProps, index) => { if (index === 0) { return ( diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index d099cdce417..03380f05e97 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -1,9 +1,14 @@ +import * as React from "react" + import { Button } from "@/registry/default/ui/button" +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" import { Step, StepItem, Stepper, useStepper, + type StepperProps, } from "@/registry/default/ui/stepper" const steps = [ @@ -13,9 +18,28 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { + const [orientation, setOrientation] = + React.useState("vertical") + return (
- + + setOrientation(value as StepperProps["orientation"]) + } + > +
+ + +
+
+ + +
+
+ {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/default/example/stepper-variants.tsx b/apps/www/registry/default/example/stepper-variants.tsx index 385e3685228..1230a4da2c9 100644 --- a/apps/www/registry/default/example/stepper-variants.tsx +++ b/apps/www/registry/default/example/stepper-variants.tsx @@ -19,7 +19,7 @@ const steps = [ export default function StepperDemo() { const [variant, setVariant] = - React.useState("circles") + React.useState("circle") return (
@@ -29,16 +29,16 @@ export default function StepperDemo() { onValueChange={(value) => setVariant(value as StepperProps["variant"])} >
- - + +
- - + +
- - + +
diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 0698a191e15..d16b7499193 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -151,7 +151,7 @@ interface StepOptions { errorIcon?: IconType onClickStep?: (step: number) => void mobileBreakpoint?: string - variant?: "circles" | "circles-alt" | "simple" + variant?: "circle" | "circle-alt" | "line" expandVerticalSteps?: boolean size?: "sm" | "md" | "lg" styles?: { @@ -176,6 +176,10 @@ interface StepOptions { /** Styles for the step description */ "step-description"?: string } + variables?: { + "--step-icon-size"?: string + "--step-gap"?: string + } scrollTracking?: boolean } interface StepperProps extends StepOptions { @@ -186,9 +190,9 @@ interface StepperProps extends StepOptions { } const VARIABLE_SIZES = { - sm: "2rem", - md: "2.25rem", - lg: "2.5rem", + sm: "36px", + md: "40px", + lg: "44px", } const Stepper = React.forwardRef( @@ -209,6 +213,7 @@ const Stepper = React.forwardRef( steps, variant, styles, + variables, scrollTracking = false, ...rest } = props @@ -255,7 +260,7 @@ const Stepper = React.forwardRef( clickable, stepCount, isVertical, - variant: variant || "circles", + variant: variant || "circle", expandVerticalSteps, steps, scrollTracking, @@ -268,14 +273,16 @@ const Stepper = React.forwardRef( "flex w-full flex-wrap", stepCount === 1 ? "justify-end" : "justify-between", orientation === "vertical" ? "flex-col" : "flex-row", - variant === "simple" && orientation === "horizontal" && "gap-4", + variant === "line" && orientation === "horizontal" && "gap-4", className, styles?.["main-container"] )} style={ { - "--step-icon-size": `${VARIABLE_SIZES[size || "md"]}`, - "--step-gap": `calc(${VARIABLE_SIZES[size || "md"]} / 8)`, + "--step-icon-size": + variables?.["--step-icon-size"] || + `${VARIABLE_SIZES[size || "md"]}`, + "--step-gap": variables?.["--step-gap"] || "8px", } as React.CSSProperties } {...rest} @@ -451,16 +458,16 @@ const verticalStepVariants = cva( { variants: { variant: { - circles: cn( + circle: cn( "pb-[var(--step-gap)] gap-[var(--step-gap)]", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", "[&:not(:last-child)]:after:absolute", - "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]", + "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]", "[&:not(:last-child)]:after:bottom-[var(--step-gap)]", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200" ), - simple: "flex-1 border-t-0 mb-4", + line: "flex-1 border-t-0 mb-4", }, }, } @@ -503,7 +510,7 @@ const VerticalStep = React.forwardRef( const localIsError = isError || state === "error" const active = - variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep const checkIcon = checkIconProp || checkIconContext const errorIcon = errorIconProp || errorIconContext @@ -526,7 +533,7 @@ const VerticalStep = React.forwardRef( className={cn( "stepper__vertical-step", verticalStepVariants({ - variant: variant?.includes("circles") ? "circles" : "simple", + variant: variant?.includes("circle") ? "circle" : "line", }), isCompletedStep && "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", @@ -547,7 +554,7 @@ const VerticalStep = React.forwardRef( className={cn( "stepper__vertical-step-container", "flex items-center", - variant === "simple" && + variant === "line" && "border-s-[3px] data-[active=true]:border-blue-500 py-2 ps-3", styles?.["vertical-step-container"] )} @@ -586,8 +593,8 @@ const VerticalStep = React.forwardRef( className={cn( "stepper__vertical-step-content", "min-h-4", - variant !== "simple" && "ps-[--step-icon-size]", - variant === "simple" && orientation === "vertical" && "min-h-0", + variant !== "line" && "ps-[--step-icon-size]", + variant === "line" && orientation === "vertical" && "min-h-0", styles?.["vertical-step-content"] )} > @@ -634,7 +641,7 @@ const HorizontalStep = React.forwardRef( const opacity = hasVisited ? 1 : 0.8 const active = - variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep const checkIcon = checkIconProp || checkIconContext const errorIcon = errorIconProp || errorIconContext @@ -648,11 +655,11 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:flex-1", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border", - variant === "circles-alt" && - "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)*2))]", - variant === "circles" && + variant === "circle-alt" && + "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]", + variant === "circle" && "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-2 [&:not(:last-child)]:after:me-2", - variant === "simple" && + variant === "line" && "flex-col flex-1 border-t-[3px] data-[active=true]:border-blue-500", isCompletedStep && "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", @@ -670,8 +677,8 @@ const HorizontalStep = React.forwardRef( className={cn( "stepper__horizontal-step-container", "flex items-center", - variant === "circles-alt" && "flex-col justify-center gap-1", - variant === "simple" && "w-full", + variant === "circle-alt" && "flex-col justify-center gap-1", + variant === "line" && "w-full", styles?.["horizontal-step-container"] )} > @@ -742,7 +749,7 @@ const StepButtonContainer = ({ const isLoading = isLoadingProp || isLoadingContext - if (variant === "simple") { + if (variant === "line") { return null } @@ -950,10 +957,10 @@ const StepLabel = ({ className={cn( "stepper__step-label-container", "flex-col flex", - variant !== "simple" ? "ms-2" : orientation === "horizontal" && "my-2", - variant === "circles-alt" && "text-center", - variant === "circles-alt" && orientation === "horizontal" && "ms-0", - variant === "circles-alt" && orientation === "vertical" && "text-start", + variant !== "line" ? "ms-2" : orientation === "horizontal" && "my-2", + variant === "circle-alt" && "text-center", + variant === "circle-alt" && orientation === "horizontal" && "ms-0", + variant === "circle-alt" && orientation === "vertical" && "text-start", styles?.["step-label-container"] )} style={{ diff --git a/apps/www/registry/new-york/example/stepper-form.tsx b/apps/www/registry/new-york/example/stepper-form.tsx index 0e6ed49a292..f43aa170144 100644 --- a/apps/www/registry/new-york/example/stepper-form.tsx +++ b/apps/www/registry/new-york/example/stepper-form.tsx @@ -26,7 +26,7 @@ const steps = [ export default function StepperDemo() { return (
- + {steps.map((stepProps, index) => { if (index === 0) { return ( diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index 7959483bacc..39032360004 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -1,9 +1,14 @@ +import * as React from "react" + import { Button } from "@/registry/new-york/ui/button" +import { Label } from "@/registry/new-york/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" import { Step, StepItem, Stepper, useStepper, + type StepperProps, } from "@/registry/new-york/ui/stepper" const steps = [ @@ -13,9 +18,28 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { + const [orientation, setOrientation] = + React.useState("vertical") + return (
- + + setOrientation(value as StepperProps["orientation"]) + } + > +
+ + +
+
+ + +
+
+ {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/new-york/example/stepper-variants.tsx b/apps/www/registry/new-york/example/stepper-variants.tsx index a7fc0494a62..c4c31fce32f 100644 --- a/apps/www/registry/new-york/example/stepper-variants.tsx +++ b/apps/www/registry/new-york/example/stepper-variants.tsx @@ -19,7 +19,7 @@ const steps = [ export default function StepperDemo() { const [variant, setVariant] = - React.useState("circles") + React.useState("circle") return (
@@ -29,16 +29,16 @@ export default function StepperDemo() { onValueChange={(value) => setVariant(value as StepperProps["variant"])} >
- - + +
- - + +
- - + +
diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 4dc77262e6c..baf31dc4f39 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -151,7 +151,7 @@ interface StepOptions { errorIcon?: IconType onClickStep?: (step: number) => void mobileBreakpoint?: string - variant?: "circles" | "circles-alt" | "simple" + variant?: "circle" | "circle-alt" | "line" expandVerticalSteps?: boolean size?: "sm" | "md" | "lg" styles?: { @@ -176,6 +176,10 @@ interface StepOptions { /** Styles for the step description */ "step-description"?: string } + variables?: { + "--step-icon-size"?: string + "--step-gap"?: string + } scrollTracking?: boolean } interface StepperProps extends StepOptions { @@ -186,9 +190,9 @@ interface StepperProps extends StepOptions { } const VARIABLE_SIZES = { - sm: "1.75rem", - md: "2rem", - lg: "2.25rem", + sm: "32px", + md: "36px", + lg: "40px", } const Stepper = React.forwardRef( @@ -209,6 +213,7 @@ const Stepper = React.forwardRef( steps, variant, styles, + variables, scrollTracking = false, ...rest } = props @@ -255,7 +260,7 @@ const Stepper = React.forwardRef( clickable, stepCount, isVertical, - variant: variant || "circles", + variant: variant || "circle", expandVerticalSteps, steps, scrollTracking, @@ -268,14 +273,16 @@ const Stepper = React.forwardRef( "flex w-full flex-wrap", stepCount === 1 ? "justify-end" : "justify-between", orientation === "vertical" ? "flex-col" : "flex-row", - variant === "simple" && orientation === "horizontal" && "gap-4", + variant === "line" && orientation === "horizontal" && "gap-4", className, styles?.["main-container"] )} style={ { - "--step-icon-size": `${VARIABLE_SIZES[size || "md"]}`, - "--step-gap": `calc(${VARIABLE_SIZES[size || "md"]} / 8)`, + "--step-icon-size": + variables?.["--step-icon-size"] || + `${VARIABLE_SIZES[size || "md"]}`, + "--step-gap": variables?.["--step-gap"] || "8px", } as React.CSSProperties } {...rest} @@ -451,16 +458,16 @@ const verticalStepVariants = cva( { variants: { variant: { - circles: cn( + circle: cn( "pb-[var(--step-gap)] gap-[var(--step-gap)]", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", "[&:not(:last-child)]:after:absolute", - "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap)*2)]", + "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]", "[&:not(:last-child)]:after:bottom-[var(--step-gap)]", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200" ), - simple: "flex-1 border-t-0 mb-4", + line: "flex-1 border-t-0 mb-4", }, }, } @@ -502,7 +509,7 @@ const VerticalStep = React.forwardRef( const localIsError = isError || state === "error" const active = - variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep const checkIcon = checkIconProp || checkIconContext const errorIcon = errorIconProp || errorIconContext @@ -525,7 +532,7 @@ const VerticalStep = React.forwardRef( className={cn( "stepper__vertical-step", verticalStepVariants({ - variant: variant?.includes("circles") ? "circles" : "simple", + variant: variant?.includes("circle") ? "circle" : "line", }), isCompletedStep && "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", @@ -544,7 +551,7 @@ const VerticalStep = React.forwardRef( className={cn( "stepper__vertical-step-container", "flex items-center", - variant === "simple" && + variant === "line" && "border-s-[3px] data-[active=true]:border-blue-500 py-2 ps-3", styles?.["vertical-step-container"] )} @@ -583,8 +590,8 @@ const VerticalStep = React.forwardRef( className={cn( "stepper__vertical-step-content", "min-h-4", - variant !== "simple" && "ps-[--step-icon-size]", - variant === "simple" && orientation === "vertical" && "min-h-0", + variant !== "line" && "ps-[--step-icon-size]", + variant === "line" && orientation === "vertical" && "min-h-0", styles?.["vertical-step-content"] )} > @@ -630,7 +637,7 @@ const HorizontalStep = React.forwardRef( const opacity = hasVisited ? 1 : 0.8 const active = - variant === "simple" ? isCompletedStep || isCurrentStep : isCompletedStep + variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep const checkIcon = checkIconProp || checkIconContext const errorIcon = errorIconProp || errorIconContext @@ -644,11 +651,11 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:flex-1", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border", - variant === "circles-alt" && - "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)*2))]", - variant === "circles" && + variant === "circle-alt" && + "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]", + variant === "circle" && "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-2 [&:not(:last-child)]:after:me-2", - variant === "simple" && + variant === "line" && "flex-col flex-1 border-t-[3px] data-[active=true]:border-blue-500", isCompletedStep && "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", @@ -664,8 +671,8 @@ const HorizontalStep = React.forwardRef( className={cn( "stepper__horizontal-step-container", "flex items-center", - variant === "circles-alt" && "flex-col justify-center gap-1", - variant === "simple" && "w-full", + variant === "circle-alt" && "flex-col justify-center gap-1", + variant === "line" && "w-full", styles?.["horizontal-step-container"] )} > @@ -736,7 +743,7 @@ const StepButtonContainer = ({ const isLoading = isLoadingProp || isLoadingContext - if (variant === "simple") { + if (variant === "line") { return null } @@ -944,10 +951,10 @@ const StepLabel = ({ className={cn( "stepper__step-label-container", "flex-col flex", - variant !== "simple" ? "ms-2" : orientation === "horizontal" && "my-2", - variant === "circles-alt" && "text-center", - variant === "circles-alt" && orientation === "horizontal" && "ms-0", - variant === "circles-alt" && orientation === "vertical" && "text-start", + variant !== "line" ? "ms-2" : orientation === "horizontal" && "my-2", + variant === "circle-alt" && "text-center", + variant === "circle-alt" && orientation === "horizontal" && "ms-0", + variant === "circle-alt" && orientation === "vertical" && "text-start", styles?.["step-label-container"] )} style={{ From a15c4a2206ef17c9bdca38598f50dcb8167cc826 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Thu, 4 Apr 2024 00:46:47 -0300 Subject: [PATCH 38/62] fix: props table colors --- apps/www/components/props-table.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/www/components/props-table.tsx b/apps/www/components/props-table.tsx index c1783b83cda..37e43c4a2e6 100644 --- a/apps/www/components/props-table.tsx +++ b/apps/www/components/props-table.tsx @@ -36,7 +36,7 @@ export function PropsTable({ return (
- + Prop @@ -62,7 +62,7 @@ export function PropsTable({
- + {name} {required ? "*" : null} @@ -94,7 +94,7 @@ export function PropsTable({
- + {Boolean(typeSimple) ? typeSimple : type} {Boolean(typeSimple) && Boolean(type) && ( @@ -132,7 +132,9 @@ export function PropsTable({ {Boolean(defaultValue) ? ( - {defaultValue} + + {defaultValue} + ) : ( Date: Thu, 4 Apr 2024 11:38:32 -0300 Subject: [PATCH 39/62] chore: add setter parameter --- apps/www/content/docs/components/stepper.mdx | 14 +++++++++++++- .../public/registry/styles/default/stepper.json | 2 +- .../registry/styles/new-york/stepper.json | 2 +- .../default/example/stepper-clickable-steps.tsx | 5 +++-- apps/www/registry/default/ui/stepper.tsx | 11 +++++++---- .../example/stepper-clickable-steps.tsx | 5 +++-- apps/www/registry/new-york/ui/stepper.tsx | 17 +++++++++++++---- 7 files changed, 41 insertions(+), 15 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 5da06b257df..bd5b9013948 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -225,6 +225,13 @@ In this example we have placed a global alert when any step is clicked. Try clic + + The `onClickStep` function has as parameters the index of the clicked step and + the setter that allows to change to that step index. The setter is useful when + we want to send an onClick event globally and we don't have access to the + useStepper hook. + + ### Footer inside the step When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To achieve this, we can simply move our footer below all the steps inside the Stepper component @@ -359,7 +366,7 @@ Finally, we also have the `variables` prop that allows you to set values for the }, { name: "onClickStep", - type: "(index: number) => void", + type: "(index: number, setStep: (index: number) => void) => void", description: "The function to be executed when a step is clicked.", }, { @@ -435,6 +442,11 @@ Finally, we also have the `variables` prop that allows you to set values for the description: "The custom icon to be displayed when the step has an error.", }, + { + name: "onClickStep", + type: "(index: number, setStep: (index: number) => void) => void", + description: "The function to be executed when a step is clicked.", + }, ]} /> diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index a592c3846ab..eae38711f34 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index 0527684d502..ce694a06c43 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index 3354ebd9882..28119c505c0 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -19,13 +19,14 @@ export default function StepperDemo() { + onClickStep={(step, setStep) => { toast({ title: "Step clicked", description: "This event is executed globally for all steps. If you want to have an event for a specific step, use the `onClickStep` prop of the independent step.", }) - } + setStep(step) + }} > {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index d16b7499193..5fc828264ca 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -149,7 +149,7 @@ interface StepOptions { responsive?: boolean checkIcon?: IconType errorIcon?: IconType - onClickStep?: (step: number) => void + onClickStep?: (step: number, setStep: (step: number) => void) => void mobileBreakpoint?: string variant?: "circle" | "circle-alt" | "line" expandVerticalSteps?: boolean @@ -367,7 +367,7 @@ interface StepProps extends React.HTMLAttributes { errorIcon?: IconType isCompletedStep?: boolean isKeepError?: boolean - onClickStep?: (step: number) => void + onClickStep?: (step: number, setStep: (step: number) => void) => void } interface StepSharedProps extends StepProps { @@ -503,6 +503,7 @@ const VerticalStep = React.forwardRef( scrollTracking, orientation, steps, + setStep, } = useStepper() const opacity = hasVisited ? 1 : 0.8 @@ -545,7 +546,8 @@ const VerticalStep = React.forwardRef( data-clickable={clickable || !!onClickStep} data-invalid={localIsError} onClick={() => - onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0) + onClickStep?.(index || 0, setStep) || + onClickStepGeneral?.(index || 0, setStep) } >
( errorIcon: errorIconContext, styles, steps, + setStep, } = useStepper() const { @@ -670,7 +673,7 @@ const HorizontalStep = React.forwardRef( data-active={active} data-invalid={localIsError} data-clickable={clickable} - onClick={() => onClickStep?.(index || 0)} + onClick={() => onClickStep?.(index || 0, setStep)} ref={ref} >
+ onClickStep={(step, setStep) => { toast({ title: "Step clicked", description: "This event is executed globally for all steps. If you want to have an event for a specific step, use the `onClickStep` prop of the independent step.", }) - } + setStep(step) + }} > {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index baf31dc4f39..88a2b5fcedb 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -149,7 +149,7 @@ interface StepOptions { responsive?: boolean checkIcon?: IconType errorIcon?: IconType - onClickStep?: (step: number) => void + onClickStep?: (step: number, setStep: (step: number) => void) => void mobileBreakpoint?: string variant?: "circle" | "circle-alt" | "line" expandVerticalSteps?: boolean @@ -367,7 +367,7 @@ interface StepProps extends React.HTMLAttributes { errorIcon?: IconType isCompletedStep?: boolean isKeepError?: boolean - onClickStep?: (step: number) => void + onClickStep?: (step: number, setStep: (step: number) => void) => void } interface StepSharedProps extends StepProps { @@ -502,6 +502,8 @@ const VerticalStep = React.forwardRef( styles, scrollTracking, orientation, + steps, + setStep, } = useStepper() const opacity = hasVisited ? 1 : 0.8 @@ -538,11 +540,14 @@ const VerticalStep = React.forwardRef( "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", styles?.["vertical-step"] )} + data-optional={steps[index || 0]?.optional} + data-completed={isCompletedStep} data-active={active} data-clickable={clickable || !!onClickStep} data-invalid={localIsError} onClick={() => - onClickStep?.(index || 0) || onClickStepGeneral?.(index || 0) + onClickStep?.(index || 0, setStep) || + onClickStepGeneral?.(index || 0, setStep) } >
( checkIcon: checkIconContext, errorIcon: errorIconContext, styles, + steps, + setStep, } = useStepper() const { @@ -661,10 +668,12 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", styles?.["horizontal-step"] )} + data-optional={steps[index || 0]?.optional} + data-completed={isCompletedStep} data-active={active} data-invalid={localIsError} data-clickable={clickable} - onClick={() => onClickStep?.(index || 0)} + onClick={() => onClickStep?.(index || 0, setStep)} ref={ref} >
Date: Sun, 7 Apr 2024 18:29:21 -0300 Subject: [PATCH 40/62] fix: pass styles to the provider --- apps/www/registry/default/ui/stepper.tsx | 1 + apps/www/registry/new-york/ui/stepper.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 5fc828264ca..39ba1b03908 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -264,6 +264,7 @@ const Stepper = React.forwardRef( expandVerticalSteps, steps, scrollTracking, + styles }} >
( expandVerticalSteps, steps, scrollTracking, + styles }} >
Date: Sun, 7 Apr 2024 19:40:15 -0300 Subject: [PATCH 41/62] chore: add custom styles docs --- apps/www/__registry__/index.tsx | 70 ++++++++++++++ apps/www/content/docs/components/stepper.mdx | 28 ++++++ .../registry/styles/default/stepper.json | 2 +- .../registry/styles/new-york/stepper.json | 2 +- .../default/example/stepper-custom-styles.tsx | 91 +++++++++++++++++++ apps/www/registry/default/ui/stepper.tsx | 28 ++---- apps/www/registry/examples.ts | 6 ++ .../example/stepper-custom-styles.tsx | 91 +++++++++++++++++++ apps/www/registry/new-york/ui/stepper.tsx | 28 ++---- 9 files changed, 302 insertions(+), 44 deletions(-) create mode 100644 apps/www/registry/default/example/stepper-custom-styles.tsx create mode 100644 apps/www/registry/new-york/example/stepper-custom-styles.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 37d8636dd44..4d1b7539de4 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -417,9 +417,11 @@ export const Index: Record = { type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/default/ui/stepper")), + source: "", files: ["registry/default/ui/stepper.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "switch": { name: "switch", @@ -1603,99 +1605,132 @@ export const Index: Record = { type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-demo")), + source: "", files: ["registry/default/example/stepper-demo.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] + }, + "stepper-custom-styles": { + name: "stepper-custom-styles", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-custom-styles")), + source: "", + files: ["registry/default/example/stepper-custom-styles.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-orientation")), + source: "", files: ["registry/default/example/stepper-orientation.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-description": { name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-description")), + source: "", files: ["registry/default/example/stepper-description.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-sizes": { name: "stepper-sizes", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-sizes")), + source: "", files: ["registry/default/example/stepper-sizes.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-form": { name: "stepper-form", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-form")), + source: "", files: ["registry/default/example/stepper-form.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-variants": { name: "stepper-variants", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-variants")), + source: "", files: ["registry/default/example/stepper-variants.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-custom-icons")), + source: "", files: ["registry/default/example/stepper-custom-icons.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-footer-inside": { name: "stepper-footer-inside", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-footer-inside")), + source: "", files: ["registry/default/example/stepper-footer-inside.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-clickable-steps")), + source: "", files: ["registry/default/example/stepper-clickable-steps.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-optional-steps")), + source: "", files: ["registry/default/example/stepper-optional-steps.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-state": { name: "stepper-state", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/default/example/stepper-state")), + source: "", files: ["registry/default/example/stepper-state.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "switch-demo": { name: "switch-demo", @@ -2915,9 +2950,11 @@ export const Index: Record = { type: "components:ui", registryDependencies: undefined, component: React.lazy(() => import("@/registry/new-york/ui/stepper")), + source: "", files: ["registry/new-york/ui/stepper.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "switch": { name: "switch", @@ -4101,99 +4138,132 @@ export const Index: Record = { type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-demo")), + source: "", files: ["registry/new-york/example/stepper-demo.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] + }, + "stepper-custom-styles": { + name: "stepper-custom-styles", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-styles")), + source: "", + files: ["registry/new-york/example/stepper-custom-styles.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] }, "stepper-orientation": { name: "stepper-orientation", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-orientation")), + source: "", files: ["registry/new-york/example/stepper-orientation.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-description": { name: "stepper-description", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-description")), + source: "", files: ["registry/new-york/example/stepper-description.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-sizes": { name: "stepper-sizes", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-sizes")), + source: "", files: ["registry/new-york/example/stepper-sizes.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-form": { name: "stepper-form", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-form")), + source: "", files: ["registry/new-york/example/stepper-form.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-variants": { name: "stepper-variants", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-variants")), + source: "", files: ["registry/new-york/example/stepper-variants.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-custom-icons": { name: "stepper-custom-icons", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-custom-icons")), + source: "", files: ["registry/new-york/example/stepper-custom-icons.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-footer-inside": { name: "stepper-footer-inside", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-footer-inside")), + source: "", files: ["registry/new-york/example/stepper-footer-inside.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-clickable-steps")), + source: "", files: ["registry/new-york/example/stepper-clickable-steps.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-optional-steps": { name: "stepper-optional-steps", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-optional-steps")), + source: "", files: ["registry/new-york/example/stepper-optional-steps.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "stepper-state": { name: "stepper-state", type: "components:example", registryDependencies: ["stepper"], component: React.lazy(() => import("@/registry/new-york/example/stepper-state")), + source: "", files: ["registry/new-york/example/stepper-state.tsx"], category: "undefined", subcategory: "undefined", + chunks: [] }, "switch-demo": { name: "switch-demo", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index bd5b9013948..540e530b7d9 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -254,6 +254,34 @@ You can also use the component with server actions. ### Custom styles +In this example we will change the style of the steps and the separator. In addition, we will also change the variables that define the size and gap of the icon for each step. + +```tsx {5-13,15-17} +... + + // Rest of the code + +... +``` + + + To customize the styles of the Steps component, `Stepper` provides a list of css classes for each part of the component. You can use these classes to override the default styles. Below is a list of the classes that are available. - `main-container`: The main container of the stepper. diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index eae38711f34..916c8bf69e2 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-9 h-9\",\n md: \"w-10 h-10\",\n lg: \"w-11 h-11\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index ce694a06c43..d185d6d7d4e 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst stepButtonVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"w-8 h-8\",\n md: \"w-9 h-9\",\n lg: \"w-10 h-10\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n size,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n \n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/stepper-custom-styles.tsx b/apps/www/registry/default/example/stepper-custom-styles.tsx new file mode 100644 index 00000000000..65b507e3c8e --- /dev/null +++ b/apps/www/registry/default/example/stepper-custom-styles.tsx @@ -0,0 +1,91 @@ +import { cn } from "@/lib/utils" +import { Button } from "@/registry/default/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/default/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + resetSteps, + hasCompletedAllSteps, + isLastStep, + isOptionalStep, + isDisabledStep, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 39ba1b03908..8b6748647ce 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -264,7 +264,7 @@ const Stepper = React.forwardRef( expandVerticalSteps, steps, scrollTracking, - styles + styles, }} >
( verticalStepVariants({ variant: variant?.includes("circle") ? "circle" : "line", }), - isCompletedStep && - "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", + "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", styles?.["vertical-step"] )} data-optional={steps[index || 0]?.optional} @@ -659,14 +659,14 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:flex-1", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border", + "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", variant === "circle-alt" && "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]", variant === "circle" && - "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-2 [&:not(:last-child)]:after:me-2", + "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-[var(--step-gap)] [&:not(:last-child)]:after:me-[var(--step-gap)]", variant === "line" && "flex-col flex-1 border-t-[3px] data-[active=true]:border-blue-500", - isCompletedStep && - "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", styles?.["horizontal-step"] )} data-optional={steps[index || 0]?.optional} @@ -720,19 +720,6 @@ type StepButtonContainerProps = StepSharedProps & { children?: React.ReactNode } -const stepButtonVariants = cva("", { - variants: { - size: { - sm: "w-9 h-9", - md: "w-10 h-10", - lg: "w-11 h-11", - }, - }, - defaultVariants: { - size: "md", - }, -}) - const StepButtonContainer = ({ isCurrentStep, isCompletedStep, @@ -746,7 +733,6 @@ const StepButtonContainer = ({ isLoading: isLoadingContext, variant, styles, - size, } = useStepper() const currentStepClickable = clickable || !!onClickStep @@ -763,12 +749,12 @@ const StepButtonContainer = ({ className={cn( "stepper__step-button-container", "rounded-full p-0 pointer-events-none", + "w-[var(--step-icon-size)] h-[var(--step-icon-size)]", "border-2 flex rounded-full justify-center items-center", "data-[clickable=true]:pointer-events-auto", "data-[active=true]:bg-blue-500 data-[active=true]:border-blue-500 data-[active=true]:text-primary-foreground dark:data-[active=true]:text-primary", "data-[current=true]:border-blue-500 data-[current=true]:bg-secondary", "data-[invalid=true]:!bg-destructive data-[invalid=true]:!border-destructive data-[invalid=true]:!text-primary-foreground dark:data-[invalid=true]:!text-primary", - stepButtonVariants({ size }), styles?.["step-button-container"] )} aria-current={isCurrentStep ? "step" : undefined} diff --git a/apps/www/registry/examples.ts b/apps/www/registry/examples.ts index 0ef0b277f96..2993a0b1daa 100644 --- a/apps/www/registry/examples.ts +++ b/apps/www/registry/examples.ts @@ -605,6 +605,12 @@ export const examples: Registry = [ registryDependencies: ["stepper"], files: ["example/stepper-demo.tsx"], }, + { + name: "stepper-custom-styles", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-custom-styles.tsx"], + }, { name: "stepper-orientation", type: "components:example", diff --git a/apps/www/registry/new-york/example/stepper-custom-styles.tsx b/apps/www/registry/new-york/example/stepper-custom-styles.tsx new file mode 100644 index 00000000000..0b9deead7d9 --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-custom-styles.tsx @@ -0,0 +1,91 @@ +import { cn } from "@/lib/utils" +import { Button } from "@/registry/new-york/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/new-york/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) +} + +const Footer = () => { + const { + nextStep, + prevStep, + resetSteps, + hasCompletedAllSteps, + isLastStep, + isOptionalStep, + isDisabledStep, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index a5a3b8103fa..1c9e002598a 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -264,7 +264,7 @@ const Stepper = React.forwardRef( expandVerticalSteps, steps, scrollTracking, - styles + styles, }} >
( verticalStepVariants({ variant: variant?.includes("circle") ? "circle" : "line", }), - isCompletedStep && - "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", + "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", styles?.["vertical-step"] )} data-optional={steps[index || 0]?.optional} @@ -659,14 +659,14 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:flex-1", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border", + "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", variant === "circle-alt" && "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]", variant === "circle" && - "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-2 [&:not(:last-child)]:after:me-2", + "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-[var(--step-gap)] [&:not(:last-child)]:after:me-[var(--step-gap)]", variant === "line" && "flex-col flex-1 border-t-[3px] data-[active=true]:border-blue-500", - isCompletedStep && - "[&:not(:last-child)]:after:bg-blue-500 [&:not(:last-child)]:after:data-[invalid=true]:bg-destructive", styles?.["horizontal-step"] )} data-optional={steps[index || 0]?.optional} @@ -720,19 +720,6 @@ type StepButtonContainerProps = StepSharedProps & { children?: React.ReactNode } -const stepButtonVariants = cva("", { - variants: { - size: { - sm: "w-8 h-8", - md: "w-9 h-9", - lg: "w-10 h-10", - }, - }, - defaultVariants: { - size: "md", - }, -}) - const StepButtonContainer = ({ isCurrentStep, isCompletedStep, @@ -746,7 +733,6 @@ const StepButtonContainer = ({ isLoading: isLoadingContext, variant, styles, - size, } = useStepper() const currentStepClickable = clickable || !!onClickStep @@ -763,12 +749,12 @@ const StepButtonContainer = ({ className={cn( "stepper__step-button-container", "rounded-full p-0 pointer-events-none", + "w-[var(--step-icon-size)] h-[var(--step-icon-size)]", "border-2 flex rounded-full justify-center items-center", "data-[clickable=true]:pointer-events-auto", "data-[active=true]:bg-blue-500 data-[active=true]:border-blue-500 data-[active=true]:text-primary-foreground dark:data-[active=true]:text-primary", "data-[current=true]:border-blue-500 data-[current=true]:bg-secondary", "data-[invalid=true]:!bg-destructive data-[invalid=true]:!border-destructive data-[invalid=true]:!text-primary-foreground dark:data-[invalid=true]:!text-primary", - stepButtonVariants({ size }), styles?.["step-button-container"] )} aria-current={isCurrentStep ? "step" : undefined} From a2d4c91b4516cc6fb5538afba4e593445c057538 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 10 Apr 2024 14:29:01 -0300 Subject: [PATCH 42/62] fix: last step spaces --- apps/www/registry/default/ui/stepper.tsx | 15 +++++++++------ apps/www/registry/new-york/ui/stepper.tsx | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 8b6748647ce..b526f3ec097 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -455,12 +455,15 @@ type VerticalStepProps = StepSharedProps & { } const verticalStepVariants = cva( - "flex flex-col relative transition-all duration-200", - { + [ + "flex flex-col relative transition-all duration-200", + "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", + ], { variants: { variant: { circle: cn( - "pb-[var(--step-gap)] gap-[var(--step-gap)]", + "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", "[&:not(:last-child)]:after:absolute", @@ -511,6 +514,8 @@ const VerticalStep = React.forwardRef( const localIsLoading = isLoading || state === "loading" const localIsError = isError || state === "error" + const isLastStep = index === steps.length - 1; + const active = variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep const checkIcon = checkIconProp || checkIconContext @@ -537,8 +542,6 @@ const VerticalStep = React.forwardRef( verticalStepVariants({ variant: variant?.includes("circle") ? "circle" : "line", }), - "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", - "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", styles?.["vertical-step"] )} data-optional={steps[index || 0]?.optional} @@ -595,7 +598,7 @@ const VerticalStep = React.forwardRef( }} className={cn( "stepper__vertical-step-content", - "min-h-4", + !isLastStep && "min-h-4", variant !== "line" && "ps-[--step-icon-size]", variant === "line" && orientation === "vertical" && "min-h-0", styles?.["vertical-step-content"] diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 1c9e002598a..da9599a2fb1 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -455,12 +455,15 @@ type VerticalStepProps = StepSharedProps & { } const verticalStepVariants = cva( - "flex flex-col relative transition-all duration-200", - { + [ + "flex flex-col relative transition-all duration-200", + "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", + ], { variants: { variant: { circle: cn( - "pb-[var(--step-gap)] gap-[var(--step-gap)]", + "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", "[&:not(:last-child)]:after:absolute", @@ -511,6 +514,8 @@ const VerticalStep = React.forwardRef( const localIsLoading = isLoading || state === "loading" const localIsError = isError || state === "error" + const isLastStep = index === steps.length - 1; + const active = variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep const checkIcon = checkIconProp || checkIconContext @@ -537,8 +542,6 @@ const VerticalStep = React.forwardRef( verticalStepVariants({ variant: variant?.includes("circle") ? "circle" : "line", }), - "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", - "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", styles?.["vertical-step"] )} data-optional={steps[index || 0]?.optional} @@ -595,7 +598,7 @@ const VerticalStep = React.forwardRef( }} className={cn( "stepper__vertical-step-content", - "min-h-4", + !isLastStep && "min-h-4", variant !== "line" && "ps-[--step-icon-size]", variant === "line" && orientation === "vertical" && "min-h-0", styles?.["vertical-step-content"] From 623faf6f3e7c2914bc14662bdc57e582255899b5 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 10 Apr 2024 14:29:13 -0300 Subject: [PATCH 43/62] chore: update docs --- apps/www/content/docs/components/stepper.mdx | 4 +- .../example/stepper-clickable-steps.tsx | 4 +- .../default/example/stepper-custom-icons.tsx | 4 +- .../default/example/stepper-custom-styles.tsx | 4 +- .../registry/default/example/stepper-demo.tsx | 4 +- .../default/example/stepper-description.tsx | 4 +- .../default/example/stepper-footer-inside.tsx | 104 ++++++------ .../example/stepper-optional-steps.tsx | 4 +- .../default/example/stepper-orientation.tsx | 160 ++++++++++-------- .../default/example/stepper-sizes.tsx | 49 +++--- .../default/example/stepper-state.tsx | 4 +- .../default/example/stepper-variants.tsx | 53 +++--- .../example/stepper-clickable-steps.tsx | 4 +- .../new-york/example/stepper-custom-icons.tsx | 4 +- .../example/stepper-custom-styles.tsx | 4 +- .../new-york/example/stepper-demo.tsx | 4 +- .../new-york/example/stepper-description.tsx | 4 +- .../example/stepper-footer-inside.tsx | 104 ++++++------ .../example/stepper-optional-steps.tsx | 4 +- .../new-york/example/stepper-orientation.tsx | 158 +++++++++-------- .../new-york/example/stepper-sizes.tsx | 49 +++--- .../new-york/example/stepper-state.tsx | 4 +- .../new-york/example/stepper-variants.tsx | 53 +++--- 23 files changed, 427 insertions(+), 363 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 540e530b7d9..1f8ba8cb87c 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -97,7 +97,7 @@ export default function StepperDemo() { {steps.map(({ label }, index) => { return ( -
+

Step {index + 1}

@@ -122,7 +122,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx index 28119c505c0..f08b10d7b3f 100644 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/default/example/stepper-clickable-steps.tsx @@ -31,7 +31,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -56,7 +56,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx index 2e79b0c855e..6d4eaa0edb2 100644 --- a/apps/www/registry/default/example/stepper-custom-icons.tsx +++ b/apps/www/registry/default/example/stepper-custom-icons.tsx @@ -21,7 +21,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -46,7 +46,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-custom-styles.tsx b/apps/www/registry/default/example/stepper-custom-styles.tsx index 65b507e3c8e..f2b3386e030 100644 --- a/apps/www/registry/default/example/stepper-custom-styles.tsx +++ b/apps/www/registry/default/example/stepper-custom-styles.tsx @@ -36,7 +36,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -61,7 +61,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx index 3d4a7a4914f..90e74192383 100644 --- a/apps/www/registry/default/example/stepper-demo.tsx +++ b/apps/www/registry/default/example/stepper-demo.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -44,7 +44,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-description.tsx b/apps/www/registry/default/example/stepper-description.tsx index fcd9712cc0e..7b77847378e 100644 --- a/apps/www/registry/default/example/stepper-description.tsx +++ b/apps/www/registry/default/example/stepper-description.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -44,7 +44,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-footer-inside.tsx b/apps/www/registry/default/example/stepper-footer-inside.tsx index 600ca2a44d2..b005ca4ca75 100644 --- a/apps/www/registry/default/example/stepper-footer-inside.tsx +++ b/apps/www/registry/default/example/stepper-footer-inside.tsx @@ -13,62 +13,62 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ) - })} - -
-
- ) + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ); + })} + +
+
+ ); } const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper() - return ( -
- - -
- ) -} + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = + useStepper(); + return ( +
+ + +
+ ); +}; const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper() + const { hasCompletedAllSteps, resetSteps } = useStepper(); - if (!hasCompletedAllSteps) { - return null - } + if (!hasCompletedAllSteps) { + return null; + } - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ) -} + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ); +}; diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx index 3f66b3b70ca..3fc8570d250 100644 --- a/apps/www/registry/default/example/stepper-optional-steps.tsx +++ b/apps/www/registry/default/example/stepper-optional-steps.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -44,7 +44,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index 03380f05e97..df3448a136f 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -17,82 +17,92 @@ const steps = [ { label: "Step 3" }, ] satisfies StepItem[] -export default function StepperDemo() { - const [orientation, setOrientation] = - React.useState("vertical") +export default function StepperOrientation() { + const [orientation, setOrientation] = + React.useState("vertical"); - return ( -
- - setOrientation(value as StepperProps["orientation"]) - } - > -
- - -
-
- - -
-
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) + return ( +
+ + setOrientation(value as StepperProps["orientation"]) + } + > + + + + + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ); + })} +
+ +
+ ); } const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} + const { + nextStep, + prevStep, + resetSteps, + isDisabledStep, + hasCompletedAllSteps, + isLastStep, + isOptionalStep, + } = useStepper(); + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ); +}; diff --git a/apps/www/registry/default/example/stepper-sizes.tsx b/apps/www/registry/default/example/stepper-sizes.tsx index db77ed451a0..76c93822957 100644 --- a/apps/www/registry/default/example/stepper-sizes.tsx +++ b/apps/www/registry/default/example/stepper-sizes.tsx @@ -22,29 +22,38 @@ export default function StepperDemo() { return (
- setSize(value as StepperProps["size"])} - > -
- - -
-
- - -
-
- - -
-
+ setSize(value as StepperProps["size"])} + > + + + + {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -69,7 +78,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-state.tsx b/apps/www/registry/default/example/stepper-state.tsx index 291128c270c..623cb192369 100644 --- a/apps/www/registry/default/example/stepper-state.tsx +++ b/apps/www/registry/default/example/stepper-state.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -45,7 +45,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/default/example/stepper-variants.tsx b/apps/www/registry/default/example/stepper-variants.tsx index 1230a4da2c9..d72783fca59 100644 --- a/apps/www/registry/default/example/stepper-variants.tsx +++ b/apps/www/registry/default/example/stepper-variants.tsx @@ -23,29 +23,42 @@ export default function StepperDemo() { return (
- setVariant(value as StepperProps["variant"])} - > -
- - -
-
- - -
-
- - -
-
+ setVariant(value as StepperProps["variant"])} + > + + + + {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -70,7 +83,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx index 802300c81e2..b11c8e205ea 100644 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx @@ -31,7 +31,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -56,7 +56,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx index 006ca9759e7..6a6074c5011 100644 --- a/apps/www/registry/new-york/example/stepper-custom-icons.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-icons.tsx @@ -21,7 +21,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -46,7 +46,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-custom-styles.tsx b/apps/www/registry/new-york/example/stepper-custom-styles.tsx index 0b9deead7d9..8ed79d0fc2e 100644 --- a/apps/www/registry/new-york/example/stepper-custom-styles.tsx +++ b/apps/www/registry/new-york/example/stepper-custom-styles.tsx @@ -36,7 +36,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -61,7 +61,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx index 3bd8839b4d2..9df5623bc4a 100644 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ b/apps/www/registry/new-york/example/stepper-demo.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -44,7 +44,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-description.tsx b/apps/www/registry/new-york/example/stepper-description.tsx index 3e51c7ab266..070afc7648e 100644 --- a/apps/www/registry/new-york/example/stepper-description.tsx +++ b/apps/www/registry/new-york/example/stepper-description.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -44,7 +44,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-footer-inside.tsx b/apps/www/registry/new-york/example/stepper-footer-inside.tsx index 073a063121d..d8008d79812 100644 --- a/apps/www/registry/new-york/example/stepper-footer-inside.tsx +++ b/apps/www/registry/new-york/example/stepper-footer-inside.tsx @@ -13,62 +13,62 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ) - })} - -
-
- ) + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ); + })} + +
+
+ ); } const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper() - return ( -
- - -
- ) -} + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = + useStepper(); + return ( +
+ + +
+ ); +}; const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper() + const { hasCompletedAllSteps, resetSteps } = useStepper(); - if (!hasCompletedAllSteps) { - return null - } + if (!hasCompletedAllSteps) { + return null; + } - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ) -} + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ); +}; diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx index 53c3bfdcc48..17eb6e48d8a 100644 --- a/apps/www/registry/new-york/example/stepper-optional-steps.tsx +++ b/apps/www/registry/new-york/example/stepper-optional-steps.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -44,7 +44,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index 39032360004..0624ba4b170 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -18,81 +18,91 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { - const [orientation, setOrientation] = - React.useState("vertical") + const [orientation, setOrientation] = + React.useState("vertical"); - return ( -
- - setOrientation(value as StepperProps["orientation"]) - } - > -
- - -
-
- - -
-
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) + return ( +
+ + setOrientation(value as StepperProps["orientation"]) + } + > + + + + + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ); + })} +
+ +
+ ); } const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} + const { + nextStep, + prevStep, + resetSteps, + isDisabledStep, + hasCompletedAllSteps, + isLastStep, + isOptionalStep, + } = useStepper(); + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ); +}; diff --git a/apps/www/registry/new-york/example/stepper-sizes.tsx b/apps/www/registry/new-york/example/stepper-sizes.tsx index 672cb00897d..f6a7994bc7c 100644 --- a/apps/www/registry/new-york/example/stepper-sizes.tsx +++ b/apps/www/registry/new-york/example/stepper-sizes.tsx @@ -22,29 +22,38 @@ export default function StepperDemo() { return (
- setSize(value as StepperProps["size"])} - > -
- - -
-
- - -
-
- - -
-
+ setSize(value as StepperProps["size"])} + > + + + + {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -69,7 +78,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-state.tsx b/apps/www/registry/new-york/example/stepper-state.tsx index d86117d9e13..4d6d4519854 100644 --- a/apps/www/registry/new-york/example/stepper-state.tsx +++ b/apps/www/registry/new-york/example/stepper-state.tsx @@ -19,7 +19,7 @@ export default function StepperDemo() { {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -45,7 +45,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} diff --git a/apps/www/registry/new-york/example/stepper-variants.tsx b/apps/www/registry/new-york/example/stepper-variants.tsx index c4c31fce32f..f22ddd394af 100644 --- a/apps/www/registry/new-york/example/stepper-variants.tsx +++ b/apps/www/registry/new-york/example/stepper-variants.tsx @@ -23,29 +23,42 @@ export default function StepperDemo() { return (
- setVariant(value as StepperProps["variant"])} - > -
- - -
-
- - -
-
- - -
-
+ setVariant(value as StepperProps["variant"])} + > + + + + {steps.map((stepProps, index) => { return ( -
+

Step {index + 1}

@@ -70,7 +83,7 @@ const Footer = () => { return ( <> {hasCompletedAllSteps && ( -
+

Woohoo! All steps completed! 🎉

)} From 1bdf45e26eb73953b0f03907628eb9c093fdf59b Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 10 Apr 2024 14:34:05 -0300 Subject: [PATCH 44/62] chore: update font weight --- apps/www/registry/default/example/stepper-orientation.tsx | 4 ++-- apps/www/registry/default/example/stepper-sizes.tsx | 6 +++--- apps/www/registry/default/example/stepper-variants.tsx | 6 +++--- apps/www/registry/new-york/example/stepper-orientation.tsx | 4 ++-- apps/www/registry/new-york/example/stepper-sizes.tsx | 6 +++--- apps/www/registry/new-york/example/stepper-variants.tsx | 6 +++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index df3448a136f..b902b165bff 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -39,14 +39,14 @@ export default function StepperOrientation() { id="horizontal" className="sr-only" /> -

Horizontal

+

Horizontal

diff --git a/apps/www/registry/default/example/stepper-sizes.tsx b/apps/www/registry/default/example/stepper-sizes.tsx index 76c93822957..38249e5c5a2 100644 --- a/apps/www/registry/default/example/stepper-sizes.tsx +++ b/apps/www/registry/default/example/stepper-sizes.tsx @@ -32,21 +32,21 @@ export default function StepperDemo() { className="flex w-fit flex-col gap-3 rounded-md border bg-background px-2 py-1 hover:bg-gray-3 [&:has([data-state=checked])]:border-primary" > -

sm

+

sm

diff --git a/apps/www/registry/default/example/stepper-variants.tsx b/apps/www/registry/default/example/stepper-variants.tsx index d72783fca59..1e1371aeef9 100644 --- a/apps/www/registry/default/example/stepper-variants.tsx +++ b/apps/www/registry/default/example/stepper-variants.tsx @@ -33,7 +33,7 @@ export default function StepperDemo() { className="flex w-fit flex-col gap-3 rounded-md border bg-background px-2 py-1 hover:bg-gray-3 [&:has([data-state=checked])]:border-primary" > -

circle

+

circle

diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index 0624ba4b170..ca25b77dac5 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -39,14 +39,14 @@ export default function StepperDemo() { id="horizontal" className="sr-only" /> -

Horizontal

+

Horizontal

diff --git a/apps/www/registry/new-york/example/stepper-sizes.tsx b/apps/www/registry/new-york/example/stepper-sizes.tsx index f6a7994bc7c..d9a3e07cf46 100644 --- a/apps/www/registry/new-york/example/stepper-sizes.tsx +++ b/apps/www/registry/new-york/example/stepper-sizes.tsx @@ -32,21 +32,21 @@ export default function StepperDemo() { className="flex w-fit flex-col gap-3 rounded-md border bg-background px-2 py-1 hover:bg-gray-3 [&:has([data-state=checked])]:border-primary" > -

sm

+

sm

diff --git a/apps/www/registry/new-york/example/stepper-variants.tsx b/apps/www/registry/new-york/example/stepper-variants.tsx index f22ddd394af..ab1a17b13df 100644 --- a/apps/www/registry/new-york/example/stepper-variants.tsx +++ b/apps/www/registry/new-york/example/stepper-variants.tsx @@ -33,7 +33,7 @@ export default function StepperDemo() { className="flex w-fit flex-col gap-3 rounded-md border bg-background px-2 py-1 hover:bg-gray-3 [&:has([data-state=checked])]:border-primary" > -

circle

+

circle

From 9a33e34bb7a073921278d82a985a0b5a764fe50f Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 14 Apr 2024 02:27:55 -0300 Subject: [PATCH 45/62] chore: update blue color to primary color --- apps/www/__registry__/index.tsx | 10 +- .../new-york/block/dashboard-06.tsx | 4 +- .../registry/styles/default/stepper.json | 2 +- .../registry/styles/new-york/stepper.json | 2 +- .../default/example/stepper-footer-inside.tsx | 104 +++--- .../default/example/stepper-orientation.tsx | 168 +++++----- .../default/example/stepper-sizes.tsx | 54 +-- .../default/example/stepper-variants.tsx | 62 ++-- apps/www/registry/default/ui/stepper.tsx | 31 +- .../new-york/block/dashboard-06-chunk-0.tsx | 307 ++++++++++++++++-- .../example/stepper-footer-inside.tsx | 104 +++--- .../new-york/example/stepper-orientation.tsx | 168 +++++----- .../new-york/example/stepper-sizes.tsx | 54 +-- .../new-york/example/stepper-variants.tsx | 62 ++-- apps/www/registry/new-york/ui/stepper.tsx | 31 +- 15 files changed, 707 insertions(+), 456 deletions(-) diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 4d1b7539de4..a936aa85fdf 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -4811,20 +4811,12 @@ export const Index: Record = { subcategory: "Dashboard", chunks: [{ name: "dashboard-06-chunk-0", - description: "A breadcrumb with two links and a page indicator.", + description: "A list of products in a table with actions. Each row has an image, name, status, price, total sales, created at and actions.", component: React.lazy(() => import("@/registry/new-york/block/dashboard-06-chunk-0")), file: "registry/new-york/block/dashboard-06-chunk-0.tsx", container: { className: "undefined" } - },{ - name: "dashboard-06-chunk-1", - description: "A list of products in a table with actions. Each row has an image, name, status, price, total sales, created at and actions.", - component: React.lazy(() => import("@/registry/new-york/block/dashboard-06-chunk-1")), - file: "registry/new-york/block/dashboard-06-chunk-1.tsx", - container: { - className: "undefined" - } }] }, "dashboard-07": { diff --git a/apps/www/__registry__/new-york/block/dashboard-06.tsx b/apps/www/__registry__/new-york/block/dashboard-06.tsx index 3936919dd9b..0311c178a8e 100644 --- a/apps/www/__registry__/new-york/block/dashboard-06.tsx +++ b/apps/www/__registry__/new-york/block/dashboard-06.tsx @@ -216,7 +216,7 @@ export default function Dashboard() { - + @@ -317,7 +317,7 @@ export default function Dashboard() {
- + Products diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index 916c8bf69e2..b0e4c1d463c 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n !isLastStep && \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index d185d6d7d4e..125735dc4fd 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n \"flex flex-col relative transition-all duration-200\",\n {\n variants: {\n variant: {\n circle: cn(\n \"pb-[var(--step-gap)] gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n !isLastStep && \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/stepper-footer-inside.tsx b/apps/www/registry/default/example/stepper-footer-inside.tsx index b005ca4ca75..af337f03436 100644 --- a/apps/www/registry/default/example/stepper-footer-inside.tsx +++ b/apps/www/registry/default/example/stepper-footer-inside.tsx @@ -13,62 +13,62 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ); - })} - -
-
- ); + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ) + })} + +
+
+ ) } const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper(); - return ( -
- - -
- ); -}; + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = + useStepper() + return ( +
+ + +
+ ) +} const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper(); + const { hasCompletedAllSteps, resetSteps } = useStepper() - if (!hasCompletedAllSteps) { - return null; - } + if (!hasCompletedAllSteps) { + return null + } - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ); -}; + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ) +} diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx index b902b165bff..7943efb9d86 100644 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ b/apps/www/registry/default/example/stepper-orientation.tsx @@ -18,91 +18,91 @@ const steps = [ ] satisfies StepItem[] export default function StepperOrientation() { - const [orientation, setOrientation] = - React.useState("vertical"); + const [orientation, setOrientation] = + React.useState("vertical") - return ( -
- - setOrientation(value as StepperProps["orientation"]) - } - > - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ); - })} -
- -
- ); + return ( +
+ + setOrientation(value as StepperProps["orientation"]) + } + > + + + + + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) } const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper(); - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ); -}; + const { + nextStep, + prevStep, + resetSteps, + isDisabledStep, + hasCompletedAllSteps, + isLastStep, + isOptionalStep, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/default/example/stepper-sizes.tsx b/apps/www/registry/default/example/stepper-sizes.tsx index 38249e5c5a2..7731e455101 100644 --- a/apps/www/registry/default/example/stepper-sizes.tsx +++ b/apps/www/registry/default/example/stepper-sizes.tsx @@ -22,33 +22,33 @@ export default function StepperDemo() { return (
- setSize(value as StepperProps["size"])} - > - - - - + setSize(value as StepperProps["size"])} + > + + + + {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/default/example/stepper-variants.tsx b/apps/www/registry/default/example/stepper-variants.tsx index 1e1371aeef9..46a0d9ccefc 100644 --- a/apps/www/registry/default/example/stepper-variants.tsx +++ b/apps/www/registry/default/example/stepper-variants.tsx @@ -23,37 +23,37 @@ export default function StepperDemo() { return (
- setVariant(value as StepperProps["variant"])} - > - - - - + setVariant(value as StepperProps["variant"])} + > + + + + {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index b526f3ec097..7890591577f 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -455,15 +455,16 @@ type VerticalStepProps = StepSharedProps & { } const verticalStepVariants = cva( - [ - "flex flex-col relative transition-all duration-200", - "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", - "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", - ], { + [ + "flex flex-col relative transition-all duration-200", + "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", + ], + { variants: { variant: { circle: cn( - "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", + "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", "[&:not(:last-child)]:after:absolute", @@ -508,13 +509,14 @@ const VerticalStep = React.forwardRef( orientation, steps, setStep, + isLastStep: isLastStepCurrentStep, } = useStepper() const opacity = hasVisited ? 1 : 0.8 const localIsLoading = isLoading || state === "loading" const localIsError = isError || state === "error" - const isLastStep = index === steps.length - 1; + const isLastStep = index === steps.length - 1 const active = variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep @@ -542,6 +544,7 @@ const VerticalStep = React.forwardRef( verticalStepVariants({ variant: variant?.includes("circle") ? "circle" : "line", }), + isLastStepCurrentStep && "gap-[var(--step-gap)]", styles?.["vertical-step"] )} data-optional={steps[index || 0]?.optional} @@ -561,7 +564,7 @@ const VerticalStep = React.forwardRef( "stepper__vertical-step-container", "flex items-center", variant === "line" && - "border-s-[3px] data-[active=true]:border-blue-500 py-2 ps-3", + "border-s-[3px] data-[active=true]:border-primary py-2 ps-3", styles?.["vertical-step-container"] )} > @@ -598,7 +601,7 @@ const VerticalStep = React.forwardRef( }} className={cn( "stepper__vertical-step-content", - !isLastStep && "min-h-4", + !isLastStep && "min-h-4", variant !== "line" && "ps-[--step-icon-size]", variant === "line" && orientation === "vertical" && "min-h-0", styles?.["vertical-step-content"] @@ -662,14 +665,14 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:flex-1", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border", - "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", variant === "circle-alt" && "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]", variant === "circle" && "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-[var(--step-gap)] [&:not(:last-child)]:after:me-[var(--step-gap)]", variant === "line" && - "flex-col flex-1 border-t-[3px] data-[active=true]:border-blue-500", + "flex-col flex-1 border-t-[3px] data-[active=true]:border-primary", styles?.["horizontal-step"] )} data-optional={steps[index || 0]?.optional} @@ -755,9 +758,9 @@ const StepButtonContainer = ({ "w-[var(--step-icon-size)] h-[var(--step-icon-size)]", "border-2 flex rounded-full justify-center items-center", "data-[clickable=true]:pointer-events-auto", - "data-[active=true]:bg-blue-500 data-[active=true]:border-blue-500 data-[active=true]:text-primary-foreground dark:data-[active=true]:text-primary", - "data-[current=true]:border-blue-500 data-[current=true]:bg-secondary", - "data-[invalid=true]:!bg-destructive data-[invalid=true]:!border-destructive data-[invalid=true]:!text-primary-foreground dark:data-[invalid=true]:!text-primary", + "data-[active=true]:bg-primary data-[active=true]:border-primary data-[active=true]:text-primary-foreground", + "data-[current=true]:border-primary data-[current=true]:bg-secondary", + "data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive", styles?.["step-button-container"] )} aria-current={isCurrentStep ? "step" : undefined} diff --git a/apps/www/registry/new-york/block/dashboard-06-chunk-0.tsx b/apps/www/registry/new-york/block/dashboard-06-chunk-0.tsx index 07f94ef4ff5..544dea9b2f2 100644 --- a/apps/www/registry/new-york/block/dashboard-06-chunk-0.tsx +++ b/apps/www/registry/new-york/block/dashboard-06-chunk-0.tsx @@ -1,34 +1,287 @@ -import Link from "next/link" +import Image from "next/image" +import { MoreHorizontal } from "lucide-react" +import { Badge } from "@/registry/new-york/ui/badge" +import { Button } from "@/registry/new-york/ui/button" import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/registry/new-york/ui/breadcrumb" + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/registry/new-york/ui/card" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/registry/new-york/ui/dropdown-menu" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/registry/new-york/ui/table" export default function Component() { return ( - - - - - Dashboard - - - - - - Products - - - - - All Products - - - + + + Products + + Manage your products and view their sales performance. + + + +
+ + + + Image + + Name + Status + Price + + Total Sales + + Created at + + Actions + + + + + + + + + + Laser Lemonade Machine + + + Draft + + $499.99 + 25 + + 2023-07-12 10:42 AM + + + + + + + + Actions + Edit + Delete + + + + + + + + + + Hypernova Headphones + + + Active + + $129.99 + 100 + + 2023-10-18 03:21 PM + + + + + + + + Actions + Edit + Delete + + + + + + + + + AeroGlow Desk Lamp + + Active + + $39.99 + 50 + + 2023-11-29 08:15 AM + + + + + + + + Actions + Edit + Delete + + + + + + + + + + TechTonic Energy Drink + + + Draft + + $2.99 + 0 + + 2023-12-25 11:59 PM + + + + + + + + Actions + Edit + Delete + + + + + + + + + + Gamer Gear Pro Controller + + + Active + + $59.99 + 75 + + 2024-01-01 12:00 AM + + + + + + + + Actions + Edit + Delete + + + + + + + + + Luminous VR Headset + + Active + + $199.99 + 30 + + 2024-02-14 02:14 PM + + + + + + + + Actions + Edit + Delete + + + + + +
+ + +
+ Showing 1-10 of 32 products +
+
+ ) } diff --git a/apps/www/registry/new-york/example/stepper-footer-inside.tsx b/apps/www/registry/new-york/example/stepper-footer-inside.tsx index d8008d79812..2e9a85db9f8 100644 --- a/apps/www/registry/new-york/example/stepper-footer-inside.tsx +++ b/apps/www/registry/new-york/example/stepper-footer-inside.tsx @@ -13,62 +13,62 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ); - })} - -
-
- ); + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ) + })} + +
+
+ ) } const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper(); - return ( -
- - -
- ); -}; + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = + useStepper() + return ( +
+ + +
+ ) +} const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper(); + const { hasCompletedAllSteps, resetSteps } = useStepper() - if (!hasCompletedAllSteps) { - return null; - } + if (!hasCompletedAllSteps) { + return null + } - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ); -}; + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ) +} diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx index ca25b77dac5..ffbeda5982a 100644 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ b/apps/www/registry/new-york/example/stepper-orientation.tsx @@ -18,91 +18,91 @@ const steps = [ ] satisfies StepItem[] export default function StepperDemo() { - const [orientation, setOrientation] = - React.useState("vertical"); + const [orientation, setOrientation] = + React.useState("vertical") - return ( -
- - setOrientation(value as StepperProps["orientation"]) - } - > - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ); - })} -
- -
- ); + return ( +
+ + setOrientation(value as StepperProps["orientation"]) + } + > + + + + + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+
+ ) + })} +
+ +
+ ) } const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper(); - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ); -}; + const { + nextStep, + prevStep, + resetSteps, + isDisabledStep, + hasCompletedAllSteps, + isLastStep, + isOptionalStep, + } = useStepper() + return ( + <> + {hasCompletedAllSteps && ( +
+

Woohoo! All steps completed! 🎉

+
+ )} +
+ {hasCompletedAllSteps ? ( + + ) : ( + <> + + + + )} +
+ + ) +} diff --git a/apps/www/registry/new-york/example/stepper-sizes.tsx b/apps/www/registry/new-york/example/stepper-sizes.tsx index d9a3e07cf46..0d7044d06b9 100644 --- a/apps/www/registry/new-york/example/stepper-sizes.tsx +++ b/apps/www/registry/new-york/example/stepper-sizes.tsx @@ -22,33 +22,33 @@ export default function StepperDemo() { return (
- setSize(value as StepperProps["size"])} - > - - - - + setSize(value as StepperProps["size"])} + > + + + + {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/new-york/example/stepper-variants.tsx b/apps/www/registry/new-york/example/stepper-variants.tsx index ab1a17b13df..0d67f8c0dc7 100644 --- a/apps/www/registry/new-york/example/stepper-variants.tsx +++ b/apps/www/registry/new-york/example/stepper-variants.tsx @@ -23,37 +23,37 @@ export default function StepperDemo() { return (
- setVariant(value as StepperProps["variant"])} - > - - - - + setVariant(value as StepperProps["variant"])} + > + + + + {steps.map((stepProps, index) => { return ( diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index da9599a2fb1..c159d3c2d42 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -455,15 +455,16 @@ type VerticalStepProps = StepSharedProps & { } const verticalStepVariants = cva( - [ - "flex flex-col relative transition-all duration-200", - "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", - "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", - ], { + [ + "flex flex-col relative transition-all duration-200", + "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", + "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", + ], + { variants: { variant: { circle: cn( - "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", + "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", "[&:not(:last-child)]:after:absolute", @@ -508,13 +509,14 @@ const VerticalStep = React.forwardRef( orientation, steps, setStep, + isLastStep: isLastStepCurrentStep, } = useStepper() const opacity = hasVisited ? 1 : 0.8 const localIsLoading = isLoading || state === "loading" const localIsError = isError || state === "error" - const isLastStep = index === steps.length - 1; + const isLastStep = index === steps.length - 1 const active = variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep @@ -542,6 +544,7 @@ const VerticalStep = React.forwardRef( verticalStepVariants({ variant: variant?.includes("circle") ? "circle" : "line", }), + isLastStepCurrentStep && "gap-[var(--step-gap)]", styles?.["vertical-step"] )} data-optional={steps[index || 0]?.optional} @@ -561,7 +564,7 @@ const VerticalStep = React.forwardRef( "stepper__vertical-step-container", "flex items-center", variant === "line" && - "border-s-[3px] data-[active=true]:border-blue-500 py-2 ps-3", + "border-s-[3px] data-[active=true]:border-primary py-2 ps-3", styles?.["vertical-step-container"] )} > @@ -598,7 +601,7 @@ const VerticalStep = React.forwardRef( }} className={cn( "stepper__vertical-step-content", - !isLastStep && "min-h-4", + !isLastStep && "min-h-4", variant !== "line" && "ps-[--step-icon-size]", variant === "line" && orientation === "vertical" && "min-h-0", styles?.["vertical-step-content"] @@ -662,14 +665,14 @@ const HorizontalStep = React.forwardRef( "[&:not(:last-child)]:flex-1", "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200", "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:h-[2px] [&:not(:last-child)]:after:bg-border", - "data-[completed=true]:[&:not(:last-child)]:after:bg-blue-500", + "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", variant === "circle-alt" && "justify-start flex-col flex-1 [&:not(:last-child)]:after:relative [&:not(:last-child)]:after:order-[-1] [&:not(:last-child)]:after:start-[50%] [&:not(:last-child)]:after:end-[50%] [&:not(:last-child)]:after:top-[calc(var(--step-icon-size)/2)] [&:not(:last-child)]:after:w-[calc((100%-var(--step-icon-size))-(var(--step-gap)))]", variant === "circle" && "[&:not(:last-child)]:after:flex-1 [&:not(:last-child)]:after:ms-[var(--step-gap)] [&:not(:last-child)]:after:me-[var(--step-gap)]", variant === "line" && - "flex-col flex-1 border-t-[3px] data-[active=true]:border-blue-500", + "flex-col flex-1 border-t-[3px] data-[active=true]:border-primary", styles?.["horizontal-step"] )} data-optional={steps[index || 0]?.optional} @@ -755,9 +758,9 @@ const StepButtonContainer = ({ "w-[var(--step-icon-size)] h-[var(--step-icon-size)]", "border-2 flex rounded-full justify-center items-center", "data-[clickable=true]:pointer-events-auto", - "data-[active=true]:bg-blue-500 data-[active=true]:border-blue-500 data-[active=true]:text-primary-foreground dark:data-[active=true]:text-primary", - "data-[current=true]:border-blue-500 data-[current=true]:bg-secondary", - "data-[invalid=true]:!bg-destructive data-[invalid=true]:!border-destructive data-[invalid=true]:!text-primary-foreground dark:data-[invalid=true]:!text-primary", + "data-[active=true]:bg-primary data-[active=true]:border-primary data-[active=true]:text-primary-foreground", + "data-[current=true]:border-primary data-[current=true]:bg-secondary", + "data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive", styles?.["step-button-container"] )} aria-current={isCurrentStep ? "step" : undefined} From 773c9dc6f48cb3d0c7b8987eedf96b3b77506fca Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Sun, 14 Apr 2024 02:39:03 -0300 Subject: [PATCH 46/62] fix: destructive text color --- apps/www/registry/default/ui/stepper.tsx | 2 +- apps/www/registry/new-york/ui/stepper.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 7890591577f..6f3db69141b 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -760,7 +760,7 @@ const StepButtonContainer = ({ "data-[clickable=true]:pointer-events-auto", "data-[active=true]:bg-primary data-[active=true]:border-primary data-[active=true]:text-primary-foreground", "data-[current=true]:border-primary data-[current=true]:bg-secondary", - "data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive", + "data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive data-[invalid=true]:text-destructive-foreground", styles?.["step-button-container"] )} aria-current={isCurrentStep ? "step" : undefined} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index c159d3c2d42..8b9c0450138 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -760,7 +760,7 @@ const StepButtonContainer = ({ "data-[clickable=true]:pointer-events-auto", "data-[active=true]:bg-primary data-[active=true]:border-primary data-[active=true]:text-primary-foreground", "data-[current=true]:border-primary data-[current=true]:bg-secondary", - "data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive", + "data-[invalid=true]:bg-destructive data-[invalid=true]:border-destructive data-[invalid=true]:text-destructive-foreground", styles?.["step-button-container"] )} aria-current={isCurrentStep ? "step" : undefined} From 820b231be85667c396dbcc7c2cc401b7f7908532 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Thu, 18 Apr 2024 23:34:43 -0300 Subject: [PATCH 47/62] fix: scroll tracking --- apps/www/registry/default/ui/stepper.tsx | 44 ++++++++++++++++++----- apps/www/registry/new-york/ui/stepper.tsx | 44 ++++++++++++++++++----- 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 6f3db69141b..8a06d7e4151 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -88,6 +88,16 @@ const StepperProvider = ({ value, children }: StepperContextProviderProps) => { // <---------- HOOKS ----------> +function usePrevious(value: T): T | undefined { + const ref = React.useRef() + + React.useEffect(() => { + ref.current = value + }, [value]) + + return ref.current +} + function useStepper() { const context = React.useContext(StepperContext) @@ -100,6 +110,8 @@ function useStepper() { const isLastStep = context.activeStep === context.steps.length - 1 const hasCompletedAllSteps = context.activeStep === context.steps.length + const previousActiveStep = usePrevious(context.activeStep) + const currentStep = context.steps[context.activeStep] const isOptionalStep = !!currentStep?.optional @@ -112,6 +124,7 @@ function useStepper() { isOptionalStep, isDisabledStep, currentStep, + previousActiveStep, } } @@ -510,6 +523,7 @@ const VerticalStep = React.forwardRef( steps, setStep, isLastStep: isLastStepCurrentStep, + previousActiveStep, } = useStepper() const opacity = hasVisited ? 1 : 0.8 @@ -527,7 +541,27 @@ const VerticalStep = React.forwardRef( if (!expandVerticalSteps) { return ( - + { + if ( + // If the step is the first step and the previous step + // was the last step or if the step is not the first step + // This prevents initial scrolling when the stepper + // is located anywhere other than the top of the view. + scrollTracking && + ((index === 0 && + previousActiveStep && + previousActiveStep === steps.length) || + (index && index > 0)) + ) { + node?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + }} + className="overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up" + > {children} @@ -591,14 +625,6 @@ const VerticalStep = React.forwardRef( />
{ - if (scrollTracking) { - node?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }} className={cn( "stepper__vertical-step-content", !isLastStep && "min-h-4", diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index 8b9c0450138..c73492c9a58 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -88,6 +88,16 @@ const StepperProvider = ({ value, children }: StepperContextProviderProps) => { // <---------- HOOKS ----------> +function usePrevious(value: T): T | undefined { + const ref = React.useRef() + + React.useEffect(() => { + ref.current = value + }, [value]) + + return ref.current +} + function useStepper() { const context = React.useContext(StepperContext) @@ -100,6 +110,8 @@ function useStepper() { const isLastStep = context.activeStep === context.steps.length - 1 const hasCompletedAllSteps = context.activeStep === context.steps.length + const previousActiveStep = usePrevious(context.activeStep) + const currentStep = context.steps[context.activeStep] const isOptionalStep = !!currentStep?.optional @@ -112,6 +124,7 @@ function useStepper() { isOptionalStep, isDisabledStep, currentStep, + previousActiveStep, } } @@ -510,6 +523,7 @@ const VerticalStep = React.forwardRef( steps, setStep, isLastStep: isLastStepCurrentStep, + previousActiveStep, } = useStepper() const opacity = hasVisited ? 1 : 0.8 @@ -527,7 +541,27 @@ const VerticalStep = React.forwardRef( if (!expandVerticalSteps) { return ( - + { + if ( + // If the step is the first step and the previous step + // was the last step or if the step is not the first step + // This prevents initial scrolling when the stepper + // is located anywhere other than the top of the view. + scrollTracking && + ((index === 0 && + previousActiveStep && + previousActiveStep === steps.length) || + (index && index > 0)) + ) { + node?.scrollIntoView({ + behavior: "smooth", + block: "center", + }) + } + }} + className="overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up" + > {children} @@ -591,14 +625,6 @@ const VerticalStep = React.forwardRef( />
{ - if (scrollTracking) { - node?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }} className={cn( "stepper__vertical-step-content", !isLastStep && "min-h-4", From 20f32792e51479901f026c09d9ff8f9282e233c3 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Thu, 18 Apr 2024 23:34:55 -0300 Subject: [PATCH 48/62] docs: add scroll tracking example --- apps/www/__registry__/index.tsx | 22 +++++ apps/www/content/docs/components/stepper.mdx | 15 ++++ .../registry/styles/default/stepper.json | 2 +- .../registry/styles/new-york/stepper.json | 2 +- .../example/stepper-scroll-tracking.tsx | 82 +++++++++++++++++++ apps/www/registry/examples.ts | 6 ++ .../example/stepper-scroll-tracking.tsx | 82 +++++++++++++++++++ 7 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 apps/www/registry/default/example/stepper-scroll-tracking.tsx create mode 100644 apps/www/registry/new-york/example/stepper-scroll-tracking.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index a936aa85fdf..14ba34e4653 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -1699,6 +1699,17 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "stepper-scroll-tracking": { + name: "stepper-scroll-tracking", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/default/example/stepper-scroll-tracking")), + source: "", + files: ["registry/default/example/stepper-scroll-tracking.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", @@ -4232,6 +4243,17 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "stepper-scroll-tracking": { + name: "stepper-scroll-tracking", + type: "components:example", + registryDependencies: ["stepper"], + component: React.lazy(() => import("@/registry/new-york/example/stepper-scroll-tracking")), + source: "", + files: ["registry/new-york/example/stepper-scroll-tracking.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "stepper-clickable-steps": { name: "stepper-clickable-steps", type: "components:example", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 1f8ba8cb87c..0bb3e2ab7b4 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -242,6 +242,16 @@ When using the vertical orientation, we may want to have the footer buttons insi If you would like the stepper to scroll to the active step when it is not in view you can do so using the `scrollTracking` prop on the Stepper component. + + + + For scroll tracking to make sense, the content of each step should ideally + include the buttons that allow the user to move forward or backward in the + stepper, since the user should be able to see the content of the active step + and not need to scroll to the end of the stepper in order to move forward or + backward. + + ### With Forms If you want to use the stepper with forms, you can do so by using the `useStepper` hook to control the component. @@ -585,5 +595,10 @@ The values returned by the hook are the following: type: "StepItem", description: "The current step object.", }, + { + name: "previousActiveStep", + type: "number", + description: "The previous active step. It allows us to know which was the previous active step", + } ]} /> diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index b0e4c1d463c..cc5fdeb5d15 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n !isLastStep && \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n \n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index 125735dc4fd..2333a5ec86b 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n \n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n {\n if (scrollTracking) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className={cn(\n \"stepper__vertical-step-content\",\n !isLastStep && \"min-h-4\",\n variant !== \"line\" && \"ps-[--step-icon-size]\",\n variant === \"line\" && orientation === \"vertical\" && \"min-h-0\",\n styles?.[\"vertical-step-content\"]\n )}\n >\n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n \n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/example/stepper-scroll-tracking.tsx b/apps/www/registry/default/example/stepper-scroll-tracking.tsx new file mode 100644 index 00000000000..5025a538bae --- /dev/null +++ b/apps/www/registry/default/example/stepper-scroll-tracking.tsx @@ -0,0 +1,82 @@ +import { Button } from "@/registry/default/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/default/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, + { label: "Step 4" }, + { label: "Step 5" }, + { label: "Step 6" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ) + })} + +
+
+ ) +} + +const StepButtons = () => { + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = + useStepper() + return ( +
+ + +
+ ) +} + +const FinalStep = () => { + const { hasCompletedAllSteps, resetSteps } = useStepper() + + if (!hasCompletedAllSteps) { + return null + } + + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ) +} diff --git a/apps/www/registry/examples.ts b/apps/www/registry/examples.ts index 2993a0b1daa..d63d5865027 100644 --- a/apps/www/registry/examples.ts +++ b/apps/www/registry/examples.ts @@ -653,6 +653,12 @@ export const examples: Registry = [ registryDependencies: ["stepper"], files: ["example/stepper-footer-inside.tsx"], }, + { + name: "stepper-scroll-tracking", + type: "components:example", + registryDependencies: ["stepper"], + files: ["example/stepper-scroll-tracking.tsx"], + }, { name: "stepper-clickable-steps", type: "components:example", diff --git a/apps/www/registry/new-york/example/stepper-scroll-tracking.tsx b/apps/www/registry/new-york/example/stepper-scroll-tracking.tsx new file mode 100644 index 00000000000..5068230505d --- /dev/null +++ b/apps/www/registry/new-york/example/stepper-scroll-tracking.tsx @@ -0,0 +1,82 @@ +import { Button } from "@/registry/new-york/ui/button" +import { + Step, + StepItem, + Stepper, + useStepper, +} from "@/registry/new-york/ui/stepper" + +const steps = [ + { label: "Step 1" }, + { label: "Step 2" }, + { label: "Step 3" }, + { label: "Step 4" }, + { label: "Step 5" }, + { label: "Step 6" }, +] satisfies StepItem[] + +export default function StepperDemo() { + return ( +
+ + {steps.map((stepProps, index) => { + return ( + +
+

Step {index + 1}

+
+ +
+ ) + })} + +
+
+ ) +} + +const StepButtons = () => { + const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = + useStepper() + return ( +
+ + +
+ ) +} + +const FinalStep = () => { + const { hasCompletedAllSteps, resetSteps } = useStepper() + + if (!hasCompletedAllSteps) { + return null + } + + return ( + <> +
+

Woohoo! All steps completed! 🎉

+
+
+ +
+ + ) +} From 4978933553051b72131f566d65ffbf4043d8c8f9 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Wed, 24 Apr 2024 10:20:20 -0300 Subject: [PATCH 49/62] fix: tab index --- apps/www/content/docs/components/stepper.mdx | 5 +++-- apps/www/public/registry/styles/default/stepper.json | 2 +- apps/www/public/registry/styles/new-york/stepper.json | 2 +- apps/www/registry/default/ui/stepper.tsx | 1 + apps/www/registry/new-york/ui/stepper.tsx | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 0bb3e2ab7b4..f3c62dbc4ae 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -598,7 +598,8 @@ The values returned by the hook are the following: { name: "previousActiveStep", type: "number", - description: "The previous active step. It allows us to know which was the previous active step", - } + description: + "The previous active step. It allows us to know which was the previous active step", + }, ]} /> diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json index cc5fdeb5d15..59ac8e3605f 100644 --- a/apps/www/public/registry/styles/default/stepper.json +++ b/apps/www/public/registry/styles/default/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n \n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n
\n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n \n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json index 2333a5ec86b..f8411bc292a 100644 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ b/apps/www/public/registry/styles/new-york/stepper.json @@ -3,7 +3,7 @@ "files": [ { "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n \n {renderChildren()}\n
\n
\n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n
\n
\n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n
\n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n
\n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n
\n \n {renderChildren()}\n
\n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" } ], "type": "components:ui" diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 8a06d7e4151..1c471a7009f 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -778,6 +778,7 @@ const StepButtonContainer = ({ return ( - ) : ( - <> - - - - )} - - - ) -} -``` - -## Examples - -### Default - - - -### Orientation - -We can pass the `orientation` prop to the Stepper component to set the orientation as "vertical" or "horizontal". - - - -### Description - -We can add a description to the array of steps - - - -### Optional steps - -If you want to make a step optional, you can add `optional: true` in the array of steps. - -In this example, the second step is optional. You can visualize the change of the label in the `Next` button - - - -### Variants - -There are 3 design variants for the Stepper component: - -- `circle`: allows you to display each step in a circular shape. The label and description are positioned horizontally next to the button of each step. -- `circle-alt`: same circular design as the circle variant but the label and description are positioned vertically below the button of each step. -- `line`: this variant features a line layout with the title and description positioned below the line. - - - -### Sizes - -The Stepper component has 3 sizes: `sm`, `md`, and `lg` which can be set using the `size` prop. - - - -### Responsive - -By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the `mobileBreakpoint` prop. - -### State - -Sometimes it is useful to display visual feedback to the user depending on some asynchronous logic. In this case we can use the `state` prop to display a loading or error indicator with the values of `loading | error`. - -This prop can be used globally within the Stepper component or locally in the Step component affected by this state. - - - -### Custom Icons - -If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component. - - - - - To change the general check and error icons, we can use the `checkIcon` and - `errorIcon` prop inside the Stepper component - - -### Clickable steps - -If you need the step buttons to be clickable and to be able to set a custom logic for the onClick event, we can use the `onClickStep` prop in the Stepper component globally or in Step locally. - -In this example we have placed a global alert when any step is clicked. Try clicking on any step to see the result. - - - - - The `onClickStep` function has as parameters the index of the clicked step and - the setter that allows to change to that step index. The setter is useful when - we want to send an onClick event globally and we don't have access to the - useStepper hook. - - -### Footer inside the step - -When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To achieve this, we can simply move our footer below all the steps inside the Stepper component - - - -### Scroll tracking - -If you would like the stepper to scroll to the active step when it is not in view you can do so using the `scrollTracking` prop on the Stepper component. - - - - - For scroll tracking to make sense, the content of each step should ideally - include the buttons that allow the user to move forward or backward in the - stepper, since the user should be able to see the content of the active step - and not need to scroll to the end of the stepper in order to move forward or - backward. - - -### With Forms - -If you want to use the stepper with forms, you can do so by using the `useStepper` hook to control the component. - -This example uses the `Form` component of shadcn and the `react-hook-form` hooks to create a form with zod for validations. - -You can also use the component with server actions. - - - -### Custom styles - -In this example we will change the style of the steps and the separator. In addition, we will also change the variables that define the size and gap of the icon for each step. - -```tsx {5-13,15-17} -... - - // Rest of the code - -... -``` - - - -To customize the styles of the Steps component, `Stepper` provides a list of css classes for each part of the component. You can use these classes to override the default styles. Below is a list of the classes that are available. - -- `main-container`: The main container of the stepper. -- `horizontal-step`: Outer wrapper for each step in horizontal layout -- `horizontal-step-container`: Inner wrapper for each step in horizontal layout -- `vertical-step`: Outer wrapper for each step in vertical layout -- `vertical-step-container`: Inner wrapper for each step in vertical layout -- `vertical-step-content`: Content wrapper for each step in vertical layout -- `step-button-container`: Wrapper for the step button -- `step-label-container`: Wrapper for the label and description -- `step-label`: The label of the step -- `step-description`: The description of the step - -In some cases you may want to customize the styles of a step based on its state. For example, you may want to change the color of a step when it is active. To do this, you can use the data attributes defined below. - -- `data-active`: The active step -- `data-invalid`: The step with an error -- `data-loading`: The step in loading state -- `data-clickable`: The step that is clickable -- `data-completed`: The step that is completed -- `data-optional`: The step that is optional - -Finally, we also have the `variables` prop that allows you to set values for the css variables that calculate the position of the separator lines. These variables can be useful when we need to set custom elements that have a different size than those offered by the component. - -- `--step-icon-size`: defines the width of the step icon. It is important to define this value in pixels. By default it has the values of the width of a shadcn/ui button depending if the style is default (`36px, 40px, 44px`) or new york (`32px, 36px, 40px`). -- `--step-gap`: defines the gap between the separator and the elements that follow it. The default value is `8px`. - -## API - -### Stepper - -", - description: "The custom icon to be displayed in the step button.", - }, - { - name: "checkIcon", - type: "LucideIcon | React.ComponentType", - description: - "The custom icon to be displayed when the step is completed.", - }, - { - name: "errorIcon", - type: "LucideIcon | React.ComponentType", - description: - "The custom icon to be displayed when the step has an error.", - }, - { - name: "responsive", - type: "boolean", - default: "true", - description: "If the stepper should be responsive.", - }, - { - name: "mobileBreakpoint", - type: "number", - default: "768px", - description: - "The breakpoint at which the stepper switches to vertical orientation.", - }, - { - name: "scrollTracking", - type: "boolean", - default: "false", - description: - "Scroll to the active step when scrolling forward or backward.", - }, - { - name: "styles", - type: "{ [key: string]: string }", - description: "Custom styles for the stepper.", - }, - { - name: "variables", - type: "{ [key: string]: string }", - description: "Custom css variables values for the stepper.", - }, - { - name: "onClickStep", - type: "(index: number, setStep: (index: number) => void) => void", - description: "The function to be executed when a step is clicked.", - }, - { - name: "variant", - type: '"circle" | "circle-alt" | "line"', - default: "circle", - description: "The design variant of the stepper.", - }, - { - name: "expandVerticalSteps", - type: "boolean", - default: "false", - description: "Control whether or not the vertical steps should collapse.", - }, - ]} -/> - -### Step - -", - description: "The custom icon to be displayed in the step button.", - }, - { - name: "state", - type: '"loading" | "error"', - description: "The state of the step (local).", - }, - { - name: "isCompletedStep", - type: "boolean", - description: - "Individually control each step state, defaults to active step.", - }, - { - name: "isKeepError", - type: "boolean", - description: - "Individually control if each step should keep showing the error state.", - }, - { - name: "checkIcon", - type: "LucideIcon | React.ComponentType", - description: - "The custom icon to be displayed when the step is completed.", - }, - { - name: "errorIcon", - type: "LucideIcon | React.ComponentType", - description: - "The custom icon to be displayed when the step has an error.", - }, - { - name: "onClickStep", - type: "(index: number, setStep: (index: number) => void) => void", - description: "The function to be executed when a step is clicked.", - }, - ]} -/> - -### useStepper - -In order to use the hook, we simply have to import it and use it inside the `` component as a wrapper. - -```tsx -import { useStepper } from "@/components/ui/stepper" - -export funcion UseStepperDemo() { - { ... } = useStepper(); - - return ( -
- { ... } -
- ) -} -``` - -The values returned by the hook are the following: - - void", - description: "Function to go to the next step.", - }, - { - name: "prevStep", - type: "() => void", - description: "Function to go to the previous step.", - }, - { - name: "setStep", - type: "(index: number) => void", - description: "Function to set a specific step.", - }, - { - name: "resetSteps", - type: "() => void", - description: "Function to reset the stepper.", - }, - { - name: "stepCount", - type: "number", - description: "The total number of steps.", - }, - { - name: "initialStep", - type: "number", - description: "The initial active step.", - }, - { - name: "clickable", - type: "boolean", - description: "If the steps are clickable.", - }, - { - name: "hasCompletedAllSteps", - type: "boolean", - description: "If all steps have been completed.", - }, - { - name: "currentStep", - type: "StepItem", - description: "The current step object.", - }, - { - name: "previousActiveStep", - type: "number", - description: - "The previous active step. It allows us to know which was the previous active step", - }, - ]} -/> diff --git a/apps/www/pages/api/components.json b/apps/www/pages/api/components.json index 2069dd82f2c..24a93c7d6c3 100644 --- a/apps/www/pages/api/components.json +++ b/apps/www/pages/api/components.json @@ -343,16 +343,6 @@ } ], "type": "ui" - }, - { - "component": "stepper", - "name": "Stepper", - "files": [ - { - "name": "stepper.tsx", - "dir": "components/ui" - } - ] }, { "name": "switch", diff --git a/apps/www/public/registry/index.json b/apps/www/public/registry/index.json index dd7e9c9f463..5d7aca8c26d 100644 --- a/apps/www/public/registry/index.json +++ b/apps/www/public/registry/index.json @@ -391,13 +391,6 @@ ], "type": "components:ui" }, - { - "name": "stepper", - "files": [ - "ui/stepper.tsx" - ], - "type": "components:ui" - }, { "name": "switch", "dependencies": [ diff --git a/apps/www/public/registry/styles/default/stepper.json b/apps/www/public/registry/styles/default/stepper.json deleted file mode 100644 index 59ac8e3605f..00000000000 --- a/apps/www/public/registry/styles/default/stepper.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "stepper", - "files": [ - { - "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/default/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"36px\",\n md: \"40px\",\n lg: \"44px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n \n \n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" - } - ], - "type": "components:ui" -} \ No newline at end of file diff --git a/apps/www/public/registry/styles/new-york/stepper.json b/apps/www/public/registry/styles/new-york/stepper.json deleted file mode 100644 index f8411bc292a..00000000000 --- a/apps/www/public/registry/styles/new-york/stepper.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "stepper", - "files": [ - { - "name": "stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport { CheckIcon, Loader2, LucideIcon, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Collapsible,\n CollapsibleContent,\n} from \"@/registry/new-york/ui/collapsible\"\n\n// <---------- CONTEXT ---------->\n\ninterface StepperContextValue extends StepperProps {\n clickable?: boolean\n isError?: boolean\n isLoading?: boolean\n isVertical?: boolean\n stepCount?: number\n expandVerticalSteps?: boolean\n activeStep: number\n initialStep: number\n}\n\nconst StepperContext = React.createContext<\n StepperContextValue & {\n nextStep: () => void\n prevStep: () => void\n resetSteps: () => void\n setStep: (step: number) => void\n }\n>({\n steps: [],\n activeStep: 0,\n initialStep: 0,\n nextStep: () => {},\n prevStep: () => {},\n resetSteps: () => {},\n setStep: () => {},\n})\n\ntype StepperContextProviderProps = {\n value: Omit\n children: React.ReactNode\n}\n\nconst StepperProvider = ({ value, children }: StepperContextProviderProps) => {\n const isError = value.state === \"error\"\n const isLoading = value.state === \"loading\"\n\n const [activeStep, setActiveStep] = React.useState(value.initialStep)\n\n const nextStep = () => {\n setActiveStep((prev) => prev + 1)\n }\n\n const prevStep = () => {\n setActiveStep((prev) => prev - 1)\n }\n\n const resetSteps = () => {\n setActiveStep(value.initialStep)\n }\n\n const setStep = (step: number) => {\n setActiveStep(step)\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- HOOKS ---------->\n\nfunction usePrevious(value: T): T | undefined {\n const ref = React.useRef()\n\n React.useEffect(() => {\n ref.current = value\n }, [value])\n\n return ref.current\n}\n\nfunction useStepper() {\n const context = React.useContext(StepperContext)\n\n if (context === undefined) {\n throw new Error(\"useStepper must be used within a StepperProvider\")\n }\n\n const { children, className, ...rest } = context\n\n const isLastStep = context.activeStep === context.steps.length - 1\n const hasCompletedAllSteps = context.activeStep === context.steps.length\n\n const previousActiveStep = usePrevious(context.activeStep)\n\n const currentStep = context.steps[context.activeStep]\n const isOptionalStep = !!currentStep?.optional\n\n const isDisabledStep = context.activeStep === 0\n\n return {\n ...rest,\n isLastStep,\n hasCompletedAllSteps,\n isOptionalStep,\n isDisabledStep,\n currentStep,\n previousActiveStep,\n }\n}\n\nfunction useMediaQuery(query: string) {\n const [value, setValue] = React.useState(false)\n\n React.useEffect(() => {\n function onChange(event: MediaQueryListEvent) {\n setValue(event.matches)\n }\n\n const result = matchMedia(query)\n result.addEventListener(\"change\", onChange)\n setValue(result.matches)\n\n return () => result.removeEventListener(\"change\", onChange)\n }, [query])\n\n return value\n}\n\n// <---------- STEPS ---------->\n\ntype StepItem = {\n id?: string\n label?: string\n description?: string\n icon?: IconType\n optional?: boolean\n}\n\ninterface StepOptions {\n orientation?: \"vertical\" | \"horizontal\"\n state?: \"loading\" | \"error\"\n responsive?: boolean\n checkIcon?: IconType\n errorIcon?: IconType\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n mobileBreakpoint?: string\n variant?: \"circle\" | \"circle-alt\" | \"line\"\n expandVerticalSteps?: boolean\n size?: \"sm\" | \"md\" | \"lg\"\n styles?: {\n /** Styles for the main container */\n \"main-container\"?: string\n /** Styles for the horizontal step */\n \"horizontal-step\"?: string\n /** Styles for the horizontal step container (button and labels) */\n \"horizontal-step-container\"?: string\n /** Styles for the vertical step */\n \"vertical-step\"?: string\n /** Styles for the vertical step container (button and labels) */\n \"vertical-step-container\"?: string\n /** Styles for the vertical step content */\n \"vertical-step-content\"?: string\n /** Styles for the step button container */\n \"step-button-container\"?: string\n /** Styles for the label and description container */\n \"step-label-container\"?: string\n /** Styles for the step label */\n \"step-label\"?: string\n /** Styles for the step description */\n \"step-description\"?: string\n }\n variables?: {\n \"--step-icon-size\"?: string\n \"--step-gap\"?: string\n }\n scrollTracking?: boolean\n}\ninterface StepperProps extends StepOptions {\n children?: React.ReactNode\n className?: string\n initialStep: number\n steps: StepItem[]\n}\n\nconst VARIABLE_SIZES = {\n sm: \"32px\",\n md: \"36px\",\n lg: \"40px\",\n}\n\nconst Stepper = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n className,\n children,\n orientation: orientationProp,\n state,\n responsive,\n checkIcon,\n errorIcon,\n onClickStep,\n mobileBreakpoint,\n expandVerticalSteps = false,\n initialStep = 0,\n size,\n steps,\n variant,\n styles,\n variables,\n scrollTracking = false,\n ...rest\n } = props\n\n const childArr = React.Children.toArray(children)\n\n const items = [] as React.ReactElement[]\n\n const footer = childArr.map((child, _index) => {\n if (!React.isValidElement(child)) {\n throw new Error(\"Stepper children must be valid React elements.\")\n }\n if (child.type === Step) {\n items.push(child)\n return null\n }\n\n return child\n })\n\n const stepCount = items.length\n\n const isMobile = useMediaQuery(\n `(max-width: ${mobileBreakpoint || \"768px\"})`\n )\n\n const clickable = !!onClickStep\n\n const orientation = isMobile && responsive ? \"vertical\" : orientationProp\n\n const isVertical = orientation === \"vertical\"\n\n return (\n \n \n {items}\n \n {orientation === \"horizontal\" && (\n {items}\n )}\n {footer}\n \n )\n }\n)\n\nStepper.defaultProps = {\n size: \"md\",\n orientation: \"horizontal\",\n responsive: true,\n}\n\nconst VerticalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n\n const childArr = React.Children.toArray(children)\n const stepCount = childArr.length\n\n return (\n <>\n {React.Children.map(children, (child, i) => {\n const isCompletedStep =\n (React.isValidElement(child) &&\n (child.props as any).isCompletedStep) ??\n i < activeStep\n const isLastStep = i === stepCount - 1\n const isCurrentStep = i === activeStep\n\n const stepProps = {\n index: i,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n }\n\n if (React.isValidElement(child)) {\n return React.cloneElement(child, stepProps)\n }\n return null\n })}\n \n )\n}\n\nconst HorizontalContent = ({ children }: { children: React.ReactNode }) => {\n const { activeStep } = useStepper()\n const childArr = React.Children.toArray(children)\n\n if (activeStep > childArr.length) {\n return null\n }\n\n return (\n <>\n {React.Children.map(childArr[activeStep], (node) => {\n if (!React.isValidElement(node)) {\n return null\n }\n return React.Children.map(node.props.children, (childNode) => childNode)\n })}\n \n )\n}\n\n// <---------- STEP ---------->\n\ninterface StepProps extends React.HTMLAttributes {\n label?: string | React.ReactNode\n description?: string\n icon?: IconType\n state?: \"loading\" | \"error\"\n checkIcon?: IconType\n errorIcon?: IconType\n isCompletedStep?: boolean\n isKeepError?: boolean\n onClickStep?: (step: number, setStep: (step: number) => void) => void\n}\n\ninterface StepSharedProps extends StepProps {\n isLastStep?: boolean\n isCurrentStep?: boolean\n index?: number\n hasVisited: boolean | undefined\n isError?: boolean\n isLoading?: boolean\n}\n\n// Props which shouldn't be passed to to the Step component from the user\ninterface StepInternalConfig {\n index: number\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isLastStep?: boolean\n}\n\ninterface FullStepProps extends StepProps, StepInternalConfig {}\n\nconst Step = React.forwardRef(\n (props, ref: React.Ref) => {\n const {\n children,\n description,\n icon,\n state,\n checkIcon,\n errorIcon,\n index,\n isCompletedStep,\n isCurrentStep,\n isLastStep,\n isKeepError,\n label,\n onClickStep,\n } = props as FullStepProps\n\n const { isVertical, isError, isLoading, clickable } = useStepper()\n\n const hasVisited = isCurrentStep || isCompletedStep\n\n const sharedProps = {\n isLastStep,\n isCompletedStep,\n isCurrentStep,\n index,\n isError,\n isLoading,\n clickable,\n label,\n description,\n hasVisited,\n icon,\n isKeepError,\n checkIcon,\n state,\n errorIcon,\n onClickStep,\n }\n\n const renderStep = () => {\n switch (isVertical) {\n case true:\n return (\n \n {children}\n \n )\n default:\n return \n }\n }\n\n return renderStep()\n }\n)\n\n// <---------- VERTICAL STEP ---------->\n\ntype VerticalStepProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst verticalStepVariants = cva(\n [\n \"flex flex-col relative transition-all duration-200\",\n \"data-[completed=true]:[&:not(:last-child)]:after:bg-primary\",\n \"data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive\",\n ],\n {\n variants: {\n variant: {\n circle: cn(\n \"[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border\",\n \"[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]\",\n \"[&:not(:last-child)]:after:absolute\",\n \"[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]\",\n \"[&:not(:last-child)]:after:bottom-[var(--step-gap)]\",\n \"[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200\"\n ),\n line: \"flex-1 border-t-0 mb-4\",\n },\n },\n }\n)\n\nconst VerticalStep = React.forwardRef(\n (props, ref) => {\n const {\n children,\n index,\n isCompletedStep,\n isCurrentStep,\n label,\n description,\n icon,\n hasVisited,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n onClickStep,\n } = props\n\n const {\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n isError,\n isLoading,\n variant,\n onClickStep: onClickStepGeneral,\n clickable,\n expandVerticalSteps,\n styles,\n scrollTracking,\n orientation,\n steps,\n setStep,\n isLastStep: isLastStepCurrentStep,\n previousActiveStep,\n } = useStepper()\n\n const opacity = hasVisited ? 1 : 0.8\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const isLastStep = index === steps.length - 1\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n const renderChildren = () => {\n if (!expandVerticalSteps) {\n return (\n \n {\n if (\n // If the step is the first step and the previous step\n // was the last step or if the step is not the first step\n // This prevents initial scrolling when the stepper\n // is located anywhere other than the top of the view.\n scrollTracking &&\n ((index === 0 &&\n previousActiveStep &&\n previousActiveStep === steps.length) ||\n (index && index > 0))\n ) {\n node?.scrollIntoView({\n behavior: \"smooth\",\n block: \"center\",\n })\n }\n }}\n className=\"overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up\"\n >\n {children}\n \n \n )\n }\n return children\n }\n\n return (\n \n onClickStep?.(index || 0, setStep) ||\n onClickStepGeneral?.(index || 0, setStep)\n }\n >\n \n \n \n \n \n \n \n {renderChildren()}\n \n \n )\n }\n)\n\n// <---------- HORIZONTAL STEP ---------->\n\nconst HorizontalStep = React.forwardRef(\n (props, ref) => {\n const {\n isError,\n isLoading,\n onClickStep,\n variant,\n clickable,\n checkIcon: checkIconContext,\n errorIcon: errorIconContext,\n styles,\n steps,\n setStep,\n } = useStepper()\n\n const {\n index,\n isCompletedStep,\n isCurrentStep,\n hasVisited,\n icon,\n label,\n description,\n isKeepError,\n state,\n checkIcon: checkIconProp,\n errorIcon: errorIconProp,\n } = props\n\n const localIsLoading = isLoading || state === \"loading\"\n const localIsError = isError || state === \"error\"\n\n const opacity = hasVisited ? 1 : 0.8\n\n const active =\n variant === \"line\" ? isCompletedStep || isCurrentStep : isCompletedStep\n\n const checkIcon = checkIconProp || checkIconContext\n const errorIcon = errorIconProp || errorIconContext\n\n return (\n onClickStep?.(index || 0, setStep)}\n ref={ref}\n >\n \n \n \n \n \n \n \n )\n }\n)\n\n// <---------- STEP BUTTON CONTAINER ---------->\n\ntype StepButtonContainerProps = StepSharedProps & {\n children?: React.ReactNode\n}\n\nconst StepButtonContainer = ({\n isCurrentStep,\n isCompletedStep,\n children,\n isError,\n isLoading: isLoadingProp,\n onClickStep,\n}: StepButtonContainerProps) => {\n const {\n clickable,\n isLoading: isLoadingContext,\n variant,\n styles,\n } = useStepper()\n\n const currentStepClickable = clickable || !!onClickStep\n\n const isLoading = isLoadingProp || isLoadingContext\n\n if (variant === \"line\") {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\n// <---------- STEP ICON ---------->\n\ntype IconType = LucideIcon | React.ComponentType | undefined\n\nconst iconVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"size-4\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\ninterface StepIconProps {\n isCompletedStep?: boolean\n isCurrentStep?: boolean\n isError?: boolean\n isLoading?: boolean\n isKeepError?: boolean\n icon?: IconType\n index?: number\n checkIcon?: IconType\n errorIcon?: IconType\n}\n\nconst StepIcon = React.forwardRef(\n (props, ref) => {\n const { size } = useStepper()\n\n const {\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n isKeepError,\n icon: CustomIcon,\n index,\n checkIcon: CustomCheckIcon,\n errorIcon: CustomErrorIcon,\n } = props\n\n const Icon = React.useMemo(\n () => (CustomIcon ? CustomIcon : null),\n [CustomIcon]\n )\n\n const ErrorIcon = React.useMemo(\n () => (CustomErrorIcon ? CustomErrorIcon : null),\n [CustomErrorIcon]\n )\n\n const Check = React.useMemo(\n () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),\n [CustomCheckIcon]\n )\n\n return React.useMemo(() => {\n if (isCompletedStep) {\n if (isError && isKeepError) {\n return (\n
\n \n
\n )\n }\n return (\n
\n \n
\n )\n }\n if (isCurrentStep) {\n if (isError && ErrorIcon) {\n return (\n
\n \n
\n )\n }\n if (isError) {\n return (\n
\n \n
\n )\n }\n if (isLoading) {\n return (\n \n )\n }\n }\n if (Icon) {\n return (\n
\n \n
\n )\n }\n return (\n \n {(index || 0) + 1}\n \n )\n }, [\n isCompletedStep,\n isCurrentStep,\n isError,\n isLoading,\n Icon,\n index,\n Check,\n ErrorIcon,\n isKeepError,\n ref,\n size,\n ])\n }\n)\n\n// <---------- STEP LABEL ---------->\n\ninterface StepLabelProps {\n isCurrentStep?: boolean\n opacity: number\n label?: string | React.ReactNode\n description?: string | null\n}\n\nconst labelVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-sm\",\n md: \"text-sm\",\n lg: \"text-base\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst descriptionVariants = cva(\"\", {\n variants: {\n size: {\n sm: \"text-xs\",\n md: \"text-xs\",\n lg: \"text-sm\",\n },\n },\n defaultVariants: {\n size: \"md\",\n },\n})\n\nconst StepLabel = ({\n isCurrentStep,\n opacity,\n label,\n description,\n}: StepLabelProps) => {\n const { variant, styles, size, orientation } = useStepper()\n const shouldRender = !!label || !!description\n\n return shouldRender ? (\n \n {!!label && (\n \n {label}\n \n )}\n {!!description && (\n \n {description}\n \n )}\n \n ) : null\n}\n\nexport { Stepper, Step, useStepper }\nexport type { StepProps, StepperProps, StepItem }\n" - } - ], - "type": "components:ui" -} \ No newline at end of file diff --git a/apps/www/registry/default/example/stepper-clickable-steps.tsx b/apps/www/registry/default/example/stepper-clickable-steps.tsx deleted file mode 100644 index f08b10d7b3f..00000000000 --- a/apps/www/registry/default/example/stepper-clickable-steps.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" -import { toast } from "@/registry/default/ui/use-toast" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- { - toast({ - title: "Step clicked", - description: - "This event is executed globally for all steps. If you want to have an event for a specific step, use the `onClickStep` prop of the independent step.", - }) - setStep(step) - }} - > - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-custom-icons.tsx b/apps/www/registry/default/example/stepper-custom-icons.tsx deleted file mode 100644 index 6d4eaa0edb2..00000000000 --- a/apps/www/registry/default/example/stepper-custom-icons.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Building, Star, User } from "lucide-react" - -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1", icon: User }, - { label: "Step 2", icon: Building }, - { label: "Step 3", icon: Star }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-custom-styles.tsx b/apps/www/registry/default/example/stepper-custom-styles.tsx deleted file mode 100644 index f2b3386e030..00000000000 --- a/apps/www/registry/default/example/stepper-custom-styles.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { cn } from "@/lib/utils" -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isDisabledStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-demo.tsx b/apps/www/registry/default/example/stepper-demo.tsx deleted file mode 100644 index 90e74192383..00000000000 --- a/apps/www/registry/default/example/stepper-demo.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isDisabledStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-description.tsx b/apps/www/registry/default/example/stepper-description.tsx deleted file mode 100644 index 7b77847378e..00000000000 --- a/apps/www/registry/default/example/stepper-description.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, - { label: "Step 3", description: "Description 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isDisabledStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-footer-inside.tsx b/apps/www/registry/default/example/stepper-footer-inside.tsx deleted file mode 100644 index af337f03436..00000000000 --- a/apps/www/registry/default/example/stepper-footer-inside.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ) - })} - -
-
- ) -} - -const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper() - return ( -
- - -
- ) -} - -const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper() - - if (!hasCompletedAllSteps) { - return null - } - - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-form.tsx b/apps/www/registry/default/example/stepper-form.tsx deleted file mode 100644 index 725ccbb19c5..00000000000 --- a/apps/www/registry/default/example/stepper-form.tsx +++ /dev/null @@ -1,191 +0,0 @@ -"use client" - -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import * as z from "zod" - -import { Button } from "@/registry/default/ui/button" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/registry/default/ui/form" -import { Input } from "@/registry/default/ui/input" -import { Step, Stepper, useStepper } from "@/registry/default/ui/stepper" -import { toast } from "@/registry/default/ui/use-toast" - -const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, -] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - if (index === 0) { - return ( - - - - ) - } - return ( - - - - ) - })} - - -
- ) -} - -const FirstFormSchema = z.object({ - username: z.string().min(2, { - message: "Username must be at least 2 characters.", - }), -}) - -function FirstStepForm() { - const { nextStep } = useStepper() - - const form = useForm>({ - resolver: zodResolver(FirstFormSchema), - defaultValues: { - username: "", - }, - }) - - function onSubmit(data: z.infer) { - nextStep() - toast({ - title: "First step submitted!", - }) - } - - return ( - - - ( - - Username - - - - - This is your public display name. - - - - )} - /> - - - - ) -} - -const SecondFormSchema = z.object({ - password: z.string().min(8, { - message: "Password must be at least 8 characters.", - }), -}) - -function SecondStepForm() { - const { nextStep } = useStepper() - - const form = useForm>({ - resolver: zodResolver(SecondFormSchema), - defaultValues: { - password: "", - }, - }) - - function onSubmit(data: z.infer) { - nextStep() - toast({ - title: "Second step submitted!", - }) - } - - return ( -
- - ( - - Password - - - - This is your private password. - - - )} - /> - - - - ) -} - -function StepperFormActions() { - const { - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - - return ( -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- ) -} - -function MyStepperFooter() { - const { activeStep, resetSteps, steps } = useStepper() - - if (activeStep !== steps.length) { - return null - } - - return ( -
- -
- ) -} diff --git a/apps/www/registry/default/example/stepper-optional-steps.tsx b/apps/www/registry/default/example/stepper-optional-steps.tsx deleted file mode 100644 index 3fc8570d250..00000000000 --- a/apps/www/registry/default/example/stepper-optional-steps.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2", optional: true }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-orientation.tsx b/apps/www/registry/default/example/stepper-orientation.tsx deleted file mode 100644 index 7943efb9d86..00000000000 --- a/apps/www/registry/default/example/stepper-orientation.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from "react" - -import { Button } from "@/registry/default/ui/button" -import { Label } from "@/registry/default/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" -import { - Step, - StepItem, - Stepper, - useStepper, - type StepperProps, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperOrientation() { - const [orientation, setOrientation] = - React.useState("vertical") - - return ( -
- - setOrientation(value as StepperProps["orientation"]) - } - > - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-scroll-tracking.tsx b/apps/www/registry/default/example/stepper-scroll-tracking.tsx deleted file mode 100644 index 5025a538bae..00000000000 --- a/apps/www/registry/default/example/stepper-scroll-tracking.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, - { label: "Step 4" }, - { label: "Step 5" }, - { label: "Step 6" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ) - })} - -
-
- ) -} - -const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper() - return ( -
- - -
- ) -} - -const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper() - - if (!hasCompletedAllSteps) { - return null - } - - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-sizes.tsx b/apps/www/registry/default/example/stepper-sizes.tsx deleted file mode 100644 index 7731e455101..00000000000 --- a/apps/www/registry/default/example/stepper-sizes.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from "react" - -import { Button } from "@/registry/default/ui/button" -import { Label } from "@/registry/default/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" -import { - Step, - StepItem, - Stepper, - StepperProps, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - const [size, setSize] = React.useState("md") - - return ( -
- setSize(value as StepperProps["size"])} - > - - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-state.tsx b/apps/www/registry/default/example/stepper-state.tsx deleted file mode 100644 index 623cb192369..00000000000 --- a/apps/www/registry/default/example/stepper-state.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Button } from "@/registry/default/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isError, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/example/stepper-variants.tsx b/apps/www/registry/default/example/stepper-variants.tsx deleted file mode 100644 index 46a0d9ccefc..00000000000 --- a/apps/www/registry/default/example/stepper-variants.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import * as React from "react" - -import { Button } from "@/registry/default/ui/button" -import { Label } from "@/registry/default/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" -import { - Step, - StepItem, - Stepper, - useStepper, - type StepperProps, -} from "@/registry/default/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - const [variant, setVariant] = - React.useState("circle") - - return ( -
- setVariant(value as StepperProps["variant"])} - > - - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx deleted file mode 100644 index 1c471a7009f..00000000000 --- a/apps/www/registry/default/ui/stepper.tsx +++ /dev/null @@ -1,1021 +0,0 @@ -"use client" - -import * as React from "react" -import { cva } from "class-variance-authority" -import { CheckIcon, Loader2, LucideIcon, X } from "lucide-react" - -import { cn } from "@/lib/utils" -import { Button } from "@/registry/default/ui/button" -import { - Collapsible, - CollapsibleContent, -} from "@/registry/default/ui/collapsible" - -// <---------- CONTEXT ----------> - -interface StepperContextValue extends StepperProps { - clickable?: boolean - isError?: boolean - isLoading?: boolean - isVertical?: boolean - stepCount?: number - expandVerticalSteps?: boolean - activeStep: number - initialStep: number -} - -const StepperContext = React.createContext< - StepperContextValue & { - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void - } ->({ - steps: [], - activeStep: 0, - initialStep: 0, - nextStep: () => {}, - prevStep: () => {}, - resetSteps: () => {}, - setStep: () => {}, -}) - -type StepperContextProviderProps = { - value: Omit - children: React.ReactNode -} - -const StepperProvider = ({ value, children }: StepperContextProviderProps) => { - const isError = value.state === "error" - const isLoading = value.state === "loading" - - const [activeStep, setActiveStep] = React.useState(value.initialStep) - - const nextStep = () => { - setActiveStep((prev) => prev + 1) - } - - const prevStep = () => { - setActiveStep((prev) => prev - 1) - } - - const resetSteps = () => { - setActiveStep(value.initialStep) - } - - const setStep = (step: number) => { - setActiveStep(step) - } - - return ( - - {children} - - ) -} - -// <---------- HOOKS ----------> - -function usePrevious(value: T): T | undefined { - const ref = React.useRef() - - React.useEffect(() => { - ref.current = value - }, [value]) - - return ref.current -} - -function useStepper() { - const context = React.useContext(StepperContext) - - if (context === undefined) { - throw new Error("useStepper must be used within a StepperProvider") - } - - const { children, className, ...rest } = context - - const isLastStep = context.activeStep === context.steps.length - 1 - const hasCompletedAllSteps = context.activeStep === context.steps.length - - const previousActiveStep = usePrevious(context.activeStep) - - const currentStep = context.steps[context.activeStep] - const isOptionalStep = !!currentStep?.optional - - const isDisabledStep = context.activeStep === 0 - - return { - ...rest, - isLastStep, - hasCompletedAllSteps, - isOptionalStep, - isDisabledStep, - currentStep, - previousActiveStep, - } -} - -function useMediaQuery(query: string) { - const [value, setValue] = React.useState(false) - - React.useEffect(() => { - function onChange(event: MediaQueryListEvent) { - setValue(event.matches) - } - - const result = matchMedia(query) - result.addEventListener("change", onChange) - setValue(result.matches) - - return () => result.removeEventListener("change", onChange) - }, [query]) - - return value -} - -// <---------- STEPS ----------> - -type StepItem = { - id?: string - label?: string - description?: string - icon?: IconType - optional?: boolean -} - -interface StepOptions { - orientation?: "vertical" | "horizontal" - state?: "loading" | "error" - responsive?: boolean - checkIcon?: IconType - errorIcon?: IconType - onClickStep?: (step: number, setStep: (step: number) => void) => void - mobileBreakpoint?: string - variant?: "circle" | "circle-alt" | "line" - expandVerticalSteps?: boolean - size?: "sm" | "md" | "lg" - styles?: { - /** Styles for the main container */ - "main-container"?: string - /** Styles for the horizontal step */ - "horizontal-step"?: string - /** Styles for the horizontal step container (button and labels) */ - "horizontal-step-container"?: string - /** Styles for the vertical step */ - "vertical-step"?: string - /** Styles for the vertical step container (button and labels) */ - "vertical-step-container"?: string - /** Styles for the vertical step content */ - "vertical-step-content"?: string - /** Styles for the step button container */ - "step-button-container"?: string - /** Styles for the label and description container */ - "step-label-container"?: string - /** Styles for the step label */ - "step-label"?: string - /** Styles for the step description */ - "step-description"?: string - } - variables?: { - "--step-icon-size"?: string - "--step-gap"?: string - } - scrollTracking?: boolean -} -interface StepperProps extends StepOptions { - children?: React.ReactNode - className?: string - initialStep: number - steps: StepItem[] -} - -const VARIABLE_SIZES = { - sm: "36px", - md: "40px", - lg: "44px", -} - -const Stepper = React.forwardRef( - (props, ref: React.Ref) => { - const { - className, - children, - orientation: orientationProp, - state, - responsive, - checkIcon, - errorIcon, - onClickStep, - mobileBreakpoint, - expandVerticalSteps = false, - initialStep = 0, - size, - steps, - variant, - styles, - variables, - scrollTracking = false, - ...rest - } = props - - const childArr = React.Children.toArray(children) - - const items = [] as React.ReactElement[] - - const footer = childArr.map((child, _index) => { - if (!React.isValidElement(child)) { - throw new Error("Stepper children must be valid React elements.") - } - if (child.type === Step) { - items.push(child) - return null - } - - return child - }) - - const stepCount = items.length - - const isMobile = useMediaQuery( - `(max-width: ${mobileBreakpoint || "768px"})` - ) - - const clickable = !!onClickStep - - const orientation = isMobile && responsive ? "vertical" : orientationProp - - const isVertical = orientation === "vertical" - - return ( - -
- {items} -
- {orientation === "horizontal" && ( - {items} - )} - {footer} -
- ) - } -) - -Stepper.defaultProps = { - size: "md", - orientation: "horizontal", - responsive: true, -} - -const VerticalContent = ({ children }: { children: React.ReactNode }) => { - const { activeStep } = useStepper() - - const childArr = React.Children.toArray(children) - const stepCount = childArr.length - - return ( - <> - {React.Children.map(children, (child, i) => { - const isCompletedStep = - (React.isValidElement(child) && - (child.props as any).isCompletedStep) ?? - i < activeStep - const isLastStep = i === stepCount - 1 - const isCurrentStep = i === activeStep - - const stepProps = { - index: i, - isCompletedStep, - isCurrentStep, - isLastStep, - } - - if (React.isValidElement(child)) { - return React.cloneElement(child, stepProps) - } - return null - })} - - ) -} - -const HorizontalContent = ({ children }: { children: React.ReactNode }) => { - const { activeStep } = useStepper() - const childArr = React.Children.toArray(children) - - if (activeStep > childArr.length) { - return null - } - - return ( - <> - {React.Children.map(childArr[activeStep], (node) => { - if (!React.isValidElement(node)) { - return null - } - return React.Children.map(node.props.children, (childNode) => childNode) - })} - - ) -} - -// <---------- STEP ----------> - -interface StepProps extends React.HTMLAttributes { - label?: string | React.ReactNode - description?: string - icon?: IconType - state?: "loading" | "error" - checkIcon?: IconType - errorIcon?: IconType - isCompletedStep?: boolean - isKeepError?: boolean - onClickStep?: (step: number, setStep: (step: number) => void) => void -} - -interface StepSharedProps extends StepProps { - isLastStep?: boolean - isCurrentStep?: boolean - index?: number - hasVisited: boolean | undefined - isError?: boolean - isLoading?: boolean -} - -// Props which shouldn't be passed to to the Step component from the user -interface StepInternalConfig { - index: number - isCompletedStep?: boolean - isCurrentStep?: boolean - isLastStep?: boolean -} - -interface FullStepProps extends StepProps, StepInternalConfig {} - -const Step = React.forwardRef( - (props, ref: React.Ref) => { - const { - children, - description, - icon, - state, - checkIcon, - errorIcon, - index, - isCompletedStep, - isCurrentStep, - isLastStep, - isKeepError, - label, - onClickStep, - } = props as FullStepProps - - const { isVertical, isError, isLoading, clickable } = useStepper() - - const hasVisited = isCurrentStep || isCompletedStep - - const sharedProps = { - isLastStep, - isCompletedStep, - isCurrentStep, - index, - isError, - isLoading, - clickable, - label, - description, - hasVisited, - icon, - isKeepError, - checkIcon, - state, - errorIcon, - onClickStep, - } - - const renderStep = () => { - switch (isVertical) { - case true: - return ( - - {children} - - ) - default: - return - } - } - - return renderStep() - } -) - -// <---------- VERTICAL STEP ----------> - -type VerticalStepProps = StepSharedProps & { - children?: React.ReactNode -} - -const verticalStepVariants = cva( - [ - "flex flex-col relative transition-all duration-200", - "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", - "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", - ], - { - variants: { - variant: { - circle: cn( - "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", - "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", - "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", - "[&:not(:last-child)]:after:absolute", - "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]", - "[&:not(:last-child)]:after:bottom-[var(--step-gap)]", - "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200" - ), - line: "flex-1 border-t-0 mb-4", - }, - }, - } -) - -const VerticalStep = React.forwardRef( - (props, ref) => { - const { - children, - index, - isCompletedStep, - isCurrentStep, - label, - description, - icon, - hasVisited, - state, - checkIcon: checkIconProp, - errorIcon: errorIconProp, - onClickStep, - } = props - - const { - checkIcon: checkIconContext, - errorIcon: errorIconContext, - isError, - isLoading, - variant, - onClickStep: onClickStepGeneral, - clickable, - expandVerticalSteps, - styles, - scrollTracking, - orientation, - steps, - setStep, - isLastStep: isLastStepCurrentStep, - previousActiveStep, - } = useStepper() - - const opacity = hasVisited ? 1 : 0.8 - const localIsLoading = isLoading || state === "loading" - const localIsError = isError || state === "error" - - const isLastStep = index === steps.length - 1 - - const active = - variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep - const checkIcon = checkIconProp || checkIconContext - const errorIcon = errorIconProp || errorIconContext - - const renderChildren = () => { - if (!expandVerticalSteps) { - return ( - - { - if ( - // If the step is the first step and the previous step - // was the last step or if the step is not the first step - // This prevents initial scrolling when the stepper - // is located anywhere other than the top of the view. - scrollTracking && - ((index === 0 && - previousActiveStep && - previousActiveStep === steps.length) || - (index && index > 0)) - ) { - node?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }} - className="overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up" - > - {children} - - - ) - } - return children - } - - return ( -
- onClickStep?.(index || 0, setStep) || - onClickStepGeneral?.(index || 0, setStep) - } - > -
- - - - -
-
- {renderChildren()} -
-
- ) - } -) - -// <---------- HORIZONTAL STEP ----------> - -const HorizontalStep = React.forwardRef( - (props, ref) => { - const { - isError, - isLoading, - onClickStep, - variant, - clickable, - checkIcon: checkIconContext, - errorIcon: errorIconContext, - styles, - steps, - setStep, - } = useStepper() - - const { - index, - isCompletedStep, - isCurrentStep, - hasVisited, - icon, - label, - description, - isKeepError, - state, - checkIcon: checkIconProp, - errorIcon: errorIconProp, - } = props - - const localIsLoading = isLoading || state === "loading" - const localIsError = isError || state === "error" - - const opacity = hasVisited ? 1 : 0.8 - - const active = - variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep - - const checkIcon = checkIconProp || checkIconContext - const errorIcon = errorIconProp || errorIconContext - - return ( -
onClickStep?.(index || 0, setStep)} - ref={ref} - > -
- - - - -
-
- ) - } -) - -// <---------- STEP BUTTON CONTAINER ----------> - -type StepButtonContainerProps = StepSharedProps & { - children?: React.ReactNode -} - -const StepButtonContainer = ({ - isCurrentStep, - isCompletedStep, - children, - isError, - isLoading: isLoadingProp, - onClickStep, -}: StepButtonContainerProps) => { - const { - clickable, - isLoading: isLoadingContext, - variant, - styles, - } = useStepper() - - const currentStepClickable = clickable || !!onClickStep - - const isLoading = isLoadingProp || isLoadingContext - - if (variant === "line") { - return null - } - - return ( - - ) -} - -// <---------- STEP ICON ----------> - -type IconType = LucideIcon | React.ComponentType | undefined - -const iconVariants = cva("", { - variants: { - size: { - sm: "size-4", - md: "size-4", - lg: "size-5", - }, - }, - defaultVariants: { - size: "md", - }, -}) - -interface StepIconProps { - isCompletedStep?: boolean - isCurrentStep?: boolean - isError?: boolean - isLoading?: boolean - isKeepError?: boolean - icon?: IconType - index?: number - checkIcon?: IconType - errorIcon?: IconType -} - -const StepIcon = React.forwardRef( - (props, ref) => { - const { size } = useStepper() - - const { - isCompletedStep, - isCurrentStep, - isError, - isLoading, - isKeepError, - icon: CustomIcon, - index, - checkIcon: CustomCheckIcon, - errorIcon: CustomErrorIcon, - } = props - - const Icon = React.useMemo( - () => (CustomIcon ? CustomIcon : null), - [CustomIcon] - ) - - const ErrorIcon = React.useMemo( - () => (CustomErrorIcon ? CustomErrorIcon : null), - [CustomErrorIcon] - ) - - const Check = React.useMemo( - () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon), - [CustomCheckIcon] - ) - - return React.useMemo(() => { - if (isCompletedStep) { - if (isError && isKeepError) { - return ( -
- -
- ) - } - return ( -
- -
- ) - } - if (isCurrentStep) { - if (isError && ErrorIcon) { - return ( -
- -
- ) - } - if (isError) { - return ( -
- -
- ) - } - if (isLoading) { - return ( - - ) - } - } - if (Icon) { - return ( -
- -
- ) - } - return ( - - {(index || 0) + 1} - - ) - }, [ - isCompletedStep, - isCurrentStep, - isError, - isLoading, - Icon, - index, - Check, - ErrorIcon, - isKeepError, - ref, - size, - ]) - } -) - -// <---------- STEP LABEL ----------> - -interface StepLabelProps { - isCurrentStep?: boolean - opacity: number - label?: string | React.ReactNode - description?: string | null -} - -const labelVariants = cva("", { - variants: { - size: { - sm: "text-sm", - md: "text-sm", - lg: "text-base", - }, - }, - defaultVariants: { - size: "md", - }, -}) - -const descriptionVariants = cva("", { - variants: { - size: { - sm: "text-xs", - md: "text-xs", - lg: "text-sm", - }, - }, - defaultVariants: { - size: "md", - }, -}) - -const StepLabel = ({ - isCurrentStep, - opacity, - label, - description, -}: StepLabelProps) => { - const { variant, styles, size, orientation } = useStepper() - const shouldRender = !!label || !!description - - return shouldRender ? ( -
- {!!label && ( - - {label} - - )} - {!!description && ( - - {description} - - )} -
- ) : null -} - -export { Stepper, Step, useStepper } -export type { StepProps, StepperProps, StepItem } diff --git a/apps/www/registry/examples.ts b/apps/www/registry/examples.ts index b97238b2ef2..9fc778a1598 100644 --- a/apps/www/registry/examples.ts +++ b/apps/www/registry/examples.ts @@ -599,84 +599,6 @@ export const examples: Registry = [ registryDependencies: ["sonner"], files: ["example/sonner-demo.tsx"], }, - { - name: "stepper-demo", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-demo.tsx"], - }, - { - name: "stepper-custom-styles", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-custom-styles.tsx"], - }, - { - name: "stepper-orientation", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-orientation.tsx"], - }, - { - name: "stepper-description", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-description.tsx"], - }, - { - name: "stepper-sizes", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-sizes.tsx"], - }, - { - name: "stepper-form", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-form.tsx"], - }, - { - name: "stepper-variants", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-variants.tsx"], - }, - { - name: "stepper-custom-icons", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-custom-icons.tsx"], - }, - { - name: "stepper-footer-inside", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-footer-inside.tsx"], - }, - { - name: "stepper-scroll-tracking", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-scroll-tracking.tsx"], - }, - { - name: "stepper-clickable-steps", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-clickable-steps.tsx"], - }, - { - name: "stepper-optional-steps", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-optional-steps.tsx"], - }, - { - name: "stepper-state", - type: "components:example", - registryDependencies: ["stepper"], - files: ["example/stepper-state.tsx"], - }, { name: "switch-demo", type: "components:example", diff --git a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx b/apps/www/registry/new-york/example/stepper-clickable-steps.tsx deleted file mode 100644 index b11c8e205ea..00000000000 --- a/apps/www/registry/new-york/example/stepper-clickable-steps.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" -import { toast } from "@/registry/new-york/ui/use-toast" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- { - toast({ - title: "Step clicked", - description: - "This event is executed globally for all steps. If you want to have an event for a specific step, use the `onClickStep` prop of the independent step.", - }) - setStep(step) - }} - > - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-custom-icons.tsx b/apps/www/registry/new-york/example/stepper-custom-icons.tsx deleted file mode 100644 index 6a6074c5011..00000000000 --- a/apps/www/registry/new-york/example/stepper-custom-icons.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Building, Star, User } from "lucide-react" - -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1", icon: User }, - { label: "Step 2", icon: Building }, - { label: "Step 3", icon: Star }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-custom-styles.tsx b/apps/www/registry/new-york/example/stepper-custom-styles.tsx deleted file mode 100644 index 8ed79d0fc2e..00000000000 --- a/apps/www/registry/new-york/example/stepper-custom-styles.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { cn } from "@/lib/utils" -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isDisabledStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-demo.tsx b/apps/www/registry/new-york/example/stepper-demo.tsx deleted file mode 100644 index 9df5623bc4a..00000000000 --- a/apps/www/registry/new-york/example/stepper-demo.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isDisabledStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-description.tsx b/apps/www/registry/new-york/example/stepper-description.tsx deleted file mode 100644 index 070afc7648e..00000000000 --- a/apps/www/registry/new-york/example/stepper-description.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, - { label: "Step 3", description: "Description 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isDisabledStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-footer-inside.tsx b/apps/www/registry/new-york/example/stepper-footer-inside.tsx deleted file mode 100644 index 2e9a85db9f8..00000000000 --- a/apps/www/registry/new-york/example/stepper-footer-inside.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ) - })} - -
-
- ) -} - -const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper() - return ( -
- - -
- ) -} - -const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper() - - if (!hasCompletedAllSteps) { - return null - } - - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-form.tsx b/apps/www/registry/new-york/example/stepper-form.tsx deleted file mode 100644 index f43aa170144..00000000000 --- a/apps/www/registry/new-york/example/stepper-form.tsx +++ /dev/null @@ -1,191 +0,0 @@ -"use client" - -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import * as z from "zod" - -import { Button } from "@/registry/new-york/ui/button" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/registry/new-york/ui/form" -import { Input } from "@/registry/new-york/ui/input" -import { Step, Stepper, useStepper } from "@/registry/new-york/ui/stepper" -import { toast } from "@/registry/new-york/ui/use-toast" - -const steps = [ - { label: "Step 1", description: "Description 1" }, - { label: "Step 2", description: "Description 2" }, -] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - if (index === 0) { - return ( - - - - ) - } - return ( - - - - ) - })} - - -
- ) -} - -const FirstFormSchema = z.object({ - username: z.string().min(2, { - message: "Username must be at least 2 characters.", - }), -}) - -function FirstStepForm() { - const { nextStep } = useStepper() - - const form = useForm>({ - resolver: zodResolver(FirstFormSchema), - defaultValues: { - username: "", - }, - }) - - function onSubmit(data: z.infer) { - nextStep() - toast({ - title: "First step submitted!", - }) - } - - return ( -
- - ( - - Username - - - - - This is your public display name. - - - - )} - /> - - - - ) -} - -const SecondFormSchema = z.object({ - password: z.string().min(8, { - message: "Password must be at least 8 characters.", - }), -}) - -function SecondStepForm() { - const { nextStep } = useStepper() - - const form = useForm>({ - resolver: zodResolver(SecondFormSchema), - defaultValues: { - password: "", - }, - }) - - function onSubmit(data: z.infer) { - nextStep() - toast({ - title: "Second step submitted!", - }) - } - - return ( -
- - ( - - Password - - - - This is your private password. - - - )} - /> - - - - ) -} - -function StepperFormActions() { - const { - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - - return ( -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- ) -} - -function MyStepperFooter() { - const { activeStep, resetSteps, steps } = useStepper() - - if (activeStep !== steps.length) { - return null - } - - return ( -
- -
- ) -} diff --git a/apps/www/registry/new-york/example/stepper-optional-steps.tsx b/apps/www/registry/new-york/example/stepper-optional-steps.tsx deleted file mode 100644 index 17eb6e48d8a..00000000000 --- a/apps/www/registry/new-york/example/stepper-optional-steps.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2", optional: true }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-orientation.tsx b/apps/www/registry/new-york/example/stepper-orientation.tsx deleted file mode 100644 index ffbeda5982a..00000000000 --- a/apps/www/registry/new-york/example/stepper-orientation.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from "react" - -import { Button } from "@/registry/new-york/ui/button" -import { Label } from "@/registry/new-york/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" -import { - Step, - StepItem, - Stepper, - useStepper, - type StepperProps, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - const [orientation, setOrientation] = - React.useState("vertical") - - return ( -
- - setOrientation(value as StepperProps["orientation"]) - } - > - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-scroll-tracking.tsx b/apps/www/registry/new-york/example/stepper-scroll-tracking.tsx deleted file mode 100644 index 5068230505d..00000000000 --- a/apps/www/registry/new-york/example/stepper-scroll-tracking.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, - { label: "Step 4" }, - { label: "Step 5" }, - { label: "Step 6" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
- -
- ) - })} - -
-
- ) -} - -const StepButtons = () => { - const { nextStep, prevStep, isLastStep, isOptionalStep, isDisabledStep } = - useStepper() - return ( -
- - -
- ) -} - -const FinalStep = () => { - const { hasCompletedAllSteps, resetSteps } = useStepper() - - if (!hasCompletedAllSteps) { - return null - } - - return ( - <> -
-

Woohoo! All steps completed! 🎉

-
-
- -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-sizes.tsx b/apps/www/registry/new-york/example/stepper-sizes.tsx deleted file mode 100644 index 0d7044d06b9..00000000000 --- a/apps/www/registry/new-york/example/stepper-sizes.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from "react" - -import { Button } from "@/registry/new-york/ui/button" -import { Label } from "@/registry/new-york/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" -import { - Step, - StepItem, - Stepper, - StepperProps, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - const [size, setSize] = React.useState("md") - - return ( -
- setSize(value as StepperProps["size"])} - > - - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-state.tsx b/apps/www/registry/new-york/example/stepper-state.tsx deleted file mode 100644 index 4d6d4519854..00000000000 --- a/apps/www/registry/new-york/example/stepper-state.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Button } from "@/registry/new-york/ui/button" -import { - Step, - StepItem, - Stepper, - useStepper, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - return ( -
- - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - isError, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/example/stepper-variants.tsx b/apps/www/registry/new-york/example/stepper-variants.tsx deleted file mode 100644 index 0d67f8c0dc7..00000000000 --- a/apps/www/registry/new-york/example/stepper-variants.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import * as React from "react" - -import { Button } from "@/registry/new-york/ui/button" -import { Label } from "@/registry/new-york/ui/label" -import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" -import { - Step, - StepItem, - Stepper, - useStepper, - type StepperProps, -} from "@/registry/new-york/ui/stepper" - -const steps = [ - { label: "Step 1" }, - { label: "Step 2" }, - { label: "Step 3" }, -] satisfies StepItem[] - -export default function StepperDemo() { - const [variant, setVariant] = - React.useState("circle") - - return ( -
- setVariant(value as StepperProps["variant"])} - > - - - - - - {steps.map((stepProps, index) => { - return ( - -
-

Step {index + 1}

-
-
- ) - })} -
- -
- ) -} - -const Footer = () => { - const { - nextStep, - prevStep, - resetSteps, - isDisabledStep, - hasCompletedAllSteps, - isLastStep, - isOptionalStep, - } = useStepper() - return ( - <> - {hasCompletedAllSteps && ( -
-

Woohoo! All steps completed! 🎉

-
- )} -
- {hasCompletedAllSteps ? ( - - ) : ( - <> - - - - )} -
- - ) -} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx deleted file mode 100644 index 694d5886664..00000000000 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ /dev/null @@ -1,1021 +0,0 @@ -"use client" - -import * as React from "react" -import { cva } from "class-variance-authority" -import { CheckIcon, Loader2, LucideIcon, X } from "lucide-react" - -import { cn } from "@/lib/utils" -import { Button } from "@/registry/new-york/ui/button" -import { - Collapsible, - CollapsibleContent, -} from "@/registry/new-york/ui/collapsible" - -// <---------- CONTEXT ----------> - -interface StepperContextValue extends StepperProps { - clickable?: boolean - isError?: boolean - isLoading?: boolean - isVertical?: boolean - stepCount?: number - expandVerticalSteps?: boolean - activeStep: number - initialStep: number -} - -const StepperContext = React.createContext< - StepperContextValue & { - nextStep: () => void - prevStep: () => void - resetSteps: () => void - setStep: (step: number) => void - } ->({ - steps: [], - activeStep: 0, - initialStep: 0, - nextStep: () => {}, - prevStep: () => {}, - resetSteps: () => {}, - setStep: () => {}, -}) - -type StepperContextProviderProps = { - value: Omit - children: React.ReactNode -} - -const StepperProvider = ({ value, children }: StepperContextProviderProps) => { - const isError = value.state === "error" - const isLoading = value.state === "loading" - - const [activeStep, setActiveStep] = React.useState(value.initialStep) - - const nextStep = () => { - setActiveStep((prev) => prev + 1) - } - - const prevStep = () => { - setActiveStep((prev) => prev - 1) - } - - const resetSteps = () => { - setActiveStep(value.initialStep) - } - - const setStep = (step: number) => { - setActiveStep(step) - } - - return ( - - {children} - - ) -} - -// <---------- HOOKS ----------> - -function usePrevious(value: T): T | undefined { - const ref = React.useRef() - - React.useEffect(() => { - ref.current = value - }, [value]) - - return ref.current -} - -function useStepper() { - const context = React.useContext(StepperContext) - - if (context === undefined) { - throw new Error("useStepper must be used within a StepperProvider") - } - - const { children, className, ...rest } = context - - const isLastStep = context.activeStep === context.steps.length - 1 - const hasCompletedAllSteps = context.activeStep === context.steps.length - - const previousActiveStep = usePrevious(context.activeStep) - - const currentStep = context.steps[context.activeStep] - const isOptionalStep = !!currentStep?.optional - - const isDisabledStep = context.activeStep === 0 - - return { - ...rest, - isLastStep, - hasCompletedAllSteps, - isOptionalStep, - isDisabledStep, - currentStep, - previousActiveStep, - } -} - -function useMediaQuery(query: string) { - const [value, setValue] = React.useState(false) - - React.useEffect(() => { - function onChange(event: MediaQueryListEvent) { - setValue(event.matches) - } - - const result = matchMedia(query) - result.addEventListener("change", onChange) - setValue(result.matches) - - return () => result.removeEventListener("change", onChange) - }, [query]) - - return value -} - -// <---------- STEPS ----------> - -type StepItem = { - id?: string - label?: string - description?: string - icon?: IconType - optional?: boolean -} - -interface StepOptions { - orientation?: "vertical" | "horizontal" - state?: "loading" | "error" - responsive?: boolean - checkIcon?: IconType - errorIcon?: IconType - onClickStep?: (step: number, setStep: (step: number) => void) => void - mobileBreakpoint?: string - variant?: "circle" | "circle-alt" | "line" - expandVerticalSteps?: boolean - size?: "sm" | "md" | "lg" - styles?: { - /** Styles for the main container */ - "main-container"?: string - /** Styles for the horizontal step */ - "horizontal-step"?: string - /** Styles for the horizontal step container (button and labels) */ - "horizontal-step-container"?: string - /** Styles for the vertical step */ - "vertical-step"?: string - /** Styles for the vertical step container (button and labels) */ - "vertical-step-container"?: string - /** Styles for the vertical step content */ - "vertical-step-content"?: string - /** Styles for the step button container */ - "step-button-container"?: string - /** Styles for the label and description container */ - "step-label-container"?: string - /** Styles for the step label */ - "step-label"?: string - /** Styles for the step description */ - "step-description"?: string - } - variables?: { - "--step-icon-size"?: string - "--step-gap"?: string - } - scrollTracking?: boolean -} -interface StepperProps extends StepOptions { - children?: React.ReactNode - className?: string - initialStep: number - steps: StepItem[] -} - -const VARIABLE_SIZES = { - sm: "32px", - md: "36px", - lg: "40px", -} - -const Stepper = React.forwardRef( - (props, ref: React.Ref) => { - const { - className, - children, - orientation: orientationProp, - state, - responsive, - checkIcon, - errorIcon, - onClickStep, - mobileBreakpoint, - expandVerticalSteps = false, - initialStep = 0, - size, - steps, - variant, - styles, - variables, - scrollTracking = false, - ...rest - } = props - - const childArr = React.Children.toArray(children) - - const items = [] as React.ReactElement[] - - const footer = childArr.map((child, _index) => { - if (!React.isValidElement(child)) { - throw new Error("Stepper children must be valid React elements.") - } - if (child.type === Step) { - items.push(child) - return null - } - - return child - }) - - const stepCount = items.length - - const isMobile = useMediaQuery( - `(max-width: ${mobileBreakpoint || "768px"})` - ) - - const clickable = !!onClickStep - - const orientation = isMobile && responsive ? "vertical" : orientationProp - - const isVertical = orientation === "vertical" - - return ( - -
- {items} -
- {orientation === "horizontal" && ( - {items} - )} - {footer} -
- ) - } -) - -Stepper.defaultProps = { - size: "md", - orientation: "horizontal", - responsive: true, -} - -const VerticalContent = ({ children }: { children: React.ReactNode }) => { - const { activeStep } = useStepper() - - const childArr = React.Children.toArray(children) - const stepCount = childArr.length - - return ( - <> - {React.Children.map(children, (child, i) => { - const isCompletedStep = - (React.isValidElement(child) && - (child.props as any).isCompletedStep) ?? - i < activeStep - const isLastStep = i === stepCount - 1 - const isCurrentStep = i === activeStep - - const stepProps = { - index: i, - isCompletedStep, - isCurrentStep, - isLastStep, - } - - if (React.isValidElement(child)) { - return React.cloneElement(child, stepProps) - } - return null - })} - - ) -} - -const HorizontalContent = ({ children }: { children: React.ReactNode }) => { - const { activeStep } = useStepper() - const childArr = React.Children.toArray(children) - - if (activeStep > childArr.length) { - return null - } - - return ( - <> - {React.Children.map(childArr[activeStep], (node) => { - if (!React.isValidElement(node)) { - return null - } - return React.Children.map(node.props.children, (childNode) => childNode) - })} - - ) -} - -// <---------- STEP ----------> - -interface StepProps extends React.HTMLAttributes { - label?: string | React.ReactNode - description?: string - icon?: IconType - state?: "loading" | "error" - checkIcon?: IconType - errorIcon?: IconType - isCompletedStep?: boolean - isKeepError?: boolean - onClickStep?: (step: number, setStep: (step: number) => void) => void -} - -interface StepSharedProps extends StepProps { - isLastStep?: boolean - isCurrentStep?: boolean - index?: number - hasVisited: boolean | undefined - isError?: boolean - isLoading?: boolean -} - -// Props which shouldn't be passed to to the Step component from the user -interface StepInternalConfig { - index: number - isCompletedStep?: boolean - isCurrentStep?: boolean - isLastStep?: boolean -} - -interface FullStepProps extends StepProps, StepInternalConfig {} - -const Step = React.forwardRef( - (props, ref: React.Ref) => { - const { - children, - description, - icon, - state, - checkIcon, - errorIcon, - index, - isCompletedStep, - isCurrentStep, - isLastStep, - isKeepError, - label, - onClickStep, - } = props as FullStepProps - - const { isVertical, isError, isLoading, clickable } = useStepper() - - const hasVisited = isCurrentStep || isCompletedStep - - const sharedProps = { - isLastStep, - isCompletedStep, - isCurrentStep, - index, - isError, - isLoading, - clickable, - label, - description, - hasVisited, - icon, - isKeepError, - checkIcon, - state, - errorIcon, - onClickStep, - } - - const renderStep = () => { - switch (isVertical) { - case true: - return ( - - {children} - - ) - default: - return - } - } - - return renderStep() - } -) - -// <---------- VERTICAL STEP ----------> - -type VerticalStepProps = StepSharedProps & { - children?: React.ReactNode -} - -const verticalStepVariants = cva( - [ - "flex flex-col relative transition-all duration-200", - "data-[completed=true]:[&:not(:last-child)]:after:bg-primary", - "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive", - ], - { - variants: { - variant: { - circle: cn( - "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]", - "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border", - "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]", - "[&:not(:last-child)]:after:absolute", - "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]", - "[&:not(:last-child)]:after:bottom-[var(--step-gap)]", - "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200" - ), - line: "flex-1 border-t-0 mb-4", - }, - }, - } -) - -const VerticalStep = React.forwardRef( - (props, ref) => { - const { - children, - index, - isCompletedStep, - isCurrentStep, - label, - description, - icon, - hasVisited, - state, - checkIcon: checkIconProp, - errorIcon: errorIconProp, - onClickStep, - } = props - - const { - checkIcon: checkIconContext, - errorIcon: errorIconContext, - isError, - isLoading, - variant, - onClickStep: onClickStepGeneral, - clickable, - expandVerticalSteps, - styles, - scrollTracking, - orientation, - steps, - setStep, - isLastStep: isLastStepCurrentStep, - previousActiveStep, - } = useStepper() - - const opacity = hasVisited ? 1 : 0.8 - const localIsLoading = isLoading || state === "loading" - const localIsError = isError || state === "error" - - const isLastStep = index === steps.length - 1 - - const active = - variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep - const checkIcon = checkIconProp || checkIconContext - const errorIcon = errorIconProp || errorIconContext - - const renderChildren = () => { - if (!expandVerticalSteps) { - return ( - - { - if ( - // If the step is the first step and the previous step - // was the last step or if the step is not the first step - // This prevents initial scrolling when the stepper - // is located anywhere other than the top of the view. - scrollTracking && - ((index === 0 && - previousActiveStep && - previousActiveStep === steps.length) || - (index && index > 0)) - ) { - node?.scrollIntoView({ - behavior: "smooth", - block: "center", - }) - } - }} - className="overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up" - > - {children} - - - ) - } - return children - } - - return ( -
- onClickStep?.(index || 0, setStep) || - onClickStepGeneral?.(index || 0, setStep) - } - > -
- - - - -
-
- {renderChildren()} -
-
- ) - } -) - -// <---------- HORIZONTAL STEP ----------> - -const HorizontalStep = React.forwardRef( - (props, ref) => { - const { - isError, - isLoading, - onClickStep, - variant, - clickable, - checkIcon: checkIconContext, - errorIcon: errorIconContext, - styles, - steps, - setStep, - } = useStepper() - - const { - index, - isCompletedStep, - isCurrentStep, - hasVisited, - icon, - label, - description, - isKeepError, - state, - checkIcon: checkIconProp, - errorIcon: errorIconProp, - } = props - - const localIsLoading = isLoading || state === "loading" - const localIsError = isError || state === "error" - - const opacity = hasVisited ? 1 : 0.8 - - const active = - variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep - - const checkIcon = checkIconProp || checkIconContext - const errorIcon = errorIconProp || errorIconContext - - return ( -
onClickStep?.(index || 0, setStep)} - ref={ref} - > -
- - - - -
-
- ) - } -) - -// <---------- STEP BUTTON CONTAINER ----------> - -type StepButtonContainerProps = StepSharedProps & { - children?: React.ReactNode -} - -const StepButtonContainer = ({ - isCurrentStep, - isCompletedStep, - children, - isError, - isLoading: isLoadingProp, - onClickStep, -}: StepButtonContainerProps) => { - const { - clickable, - isLoading: isLoadingContext, - variant, - styles, - } = useStepper() - - const currentStepClickable = clickable || !!onClickStep - - const isLoading = isLoadingProp || isLoadingContext - - if (variant === "line") { - return null - } - - return ( - - ) -} - -// <---------- STEP ICON ----------> - -type IconType = LucideIcon | React.ComponentType | undefined - -const iconVariants = cva("", { - variants: { - size: { - sm: "size-4", - md: "size-4", - lg: "size-5", - }, - }, - defaultVariants: { - size: "md", - }, -}) - -interface StepIconProps { - isCompletedStep?: boolean - isCurrentStep?: boolean - isError?: boolean - isLoading?: boolean - isKeepError?: boolean - icon?: IconType - index?: number - checkIcon?: IconType - errorIcon?: IconType -} - -const StepIcon = React.forwardRef( - (props, ref) => { - const { size } = useStepper() - - const { - isCompletedStep, - isCurrentStep, - isError, - isLoading, - isKeepError, - icon: CustomIcon, - index, - checkIcon: CustomCheckIcon, - errorIcon: CustomErrorIcon, - } = props - - const Icon = React.useMemo( - () => (CustomIcon ? CustomIcon : null), - [CustomIcon] - ) - - const ErrorIcon = React.useMemo( - () => (CustomErrorIcon ? CustomErrorIcon : null), - [CustomErrorIcon] - ) - - const Check = React.useMemo( - () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon), - [CustomCheckIcon] - ) - - return React.useMemo(() => { - if (isCompletedStep) { - if (isError && isKeepError) { - return ( -
- -
- ) - } - return ( -
- -
- ) - } - if (isCurrentStep) { - if (isError && ErrorIcon) { - return ( -
- -
- ) - } - if (isError) { - return ( -
- -
- ) - } - if (isLoading) { - return ( - - ) - } - } - if (Icon) { - return ( -
- -
- ) - } - return ( - - {(index || 0) + 1} - - ) - }, [ - isCompletedStep, - isCurrentStep, - isError, - isLoading, - Icon, - index, - Check, - ErrorIcon, - isKeepError, - ref, - size, - ]) - } -) - -// <---------- STEP LABEL ----------> - -interface StepLabelProps { - isCurrentStep?: boolean - opacity: number - label?: string | React.ReactNode - description?: string | null -} - -const labelVariants = cva("", { - variants: { - size: { - sm: "text-sm", - md: "text-sm", - lg: "text-base", - }, - }, - defaultVariants: { - size: "md", - }, -}) - -const descriptionVariants = cva("", { - variants: { - size: { - sm: "text-xs", - md: "text-xs", - lg: "text-sm", - }, - }, - defaultVariants: { - size: "md", - }, -}) - -const StepLabel = ({ - isCurrentStep, - opacity, - label, - description, -}: StepLabelProps) => { - const { variant, styles, size, orientation } = useStepper() - const shouldRender = !!label || !!description - - return shouldRender ? ( -
- {!!label && ( - - {label} - - )} - {!!description && ( - - {description} - - )} -
- ) : null -} - -export { Stepper, Step, useStepper } -export type { StepProps, StepperProps, StepItem } diff --git a/apps/www/registry/ui.ts b/apps/www/registry/ui.ts index e34770d22d8..7dcd1ec8b57 100644 --- a/apps/www/registry/ui.ts +++ b/apps/www/registry/ui.ts @@ -236,11 +236,6 @@ export const ui: Registry = [ dependencies: ["sonner", "next-themes"], files: ["ui/sonner.tsx"], }, - { - name: "stepper", - type: "components:ui", - files: ["ui/stepper.tsx"], - }, { name: "switch", type: "components:ui", diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 3e40619fdf2..53f63eb4aae 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -67,10 +67,6 @@ module.exports = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, - "collapsible-down": { - from: { height: "0" }, - to: { height: "var(--radix-collapsible-content-height)" }, - }, "collapsible-up": { from: { height: "var(--radix-collapsible-content-height)" }, to: { height: "0" }, @@ -84,7 +80,6 @@ module.exports = { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", "caret-blink": "caret-blink 1.25s ease-out infinite", - "collapsible-down": "collapsible-down 0.2s ease-out", "collapsible-up": "collapsible-up 0.2s ease-out", }, }, From 5fa99b04f49124965c0dff1a43c6205ea285be65 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 20 Jan 2025 00:56:49 -0300 Subject: [PATCH 51/62] clean pr to initialize new clean version --- apps/www/components/mdx-components.tsx | 2 - apps/www/components/props-table.tsx | 152 ------------------------- tailwind.config.cjs | 5 - 3 files changed, 159 deletions(-) delete mode 100644 apps/www/components/props-table.tsx diff --git a/apps/www/components/mdx-components.tsx b/apps/www/components/mdx-components.tsx index bec652dd9a2..680ae3ce7f9 100644 --- a/apps/www/components/mdx-components.tsx +++ b/apps/www/components/mdx-components.tsx @@ -18,7 +18,6 @@ import { ComponentPreview } from "@/components/component-preview" import { ComponentSource } from "@/components/component-source" import { CopyButton, CopyNpmCommandButton } from "@/components/copy-button" import { FrameworkDocs } from "@/components/framework-docs" -import { PropsTable } from "@/components/props-table" import { StyleWrapper } from "@/components/style-wrapper" import { Accordion, @@ -239,7 +238,6 @@ const components = { ), Image, Callout, - PropsTable, ComponentPreview, ComponentExample, ComponentSource, diff --git a/apps/www/components/props-table.tsx b/apps/www/components/props-table.tsx deleted file mode 100644 index 37e43c4a2e6..00000000000 --- a/apps/www/components/props-table.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from "react" -import { DividerHorizontalIcon, InfoCircledIcon } from "@radix-ui/react-icons" - -import { Button } from "@/registry/new-york/ui/button" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/registry/new-york/ui/popover" -import { ScrollArea } from "@/registry/new-york/ui/scroll-area" -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/registry/new-york/ui/table" - -export type PropDef = { - name: string - required?: boolean - default?: string | boolean - type?: string - typeSimple: string - description?: string | React.ReactNode -} - -export function PropsTable({ - data, - propHeaderFixedWidth = true, -}: { - data: PropDef[] - propHeaderFixedWidth?: boolean -}) { - return ( -
- - - - - Prop - - Type - Default - - - - {data.map( - ( - { - name, - type, - typeSimple, - required, - default: defaultValue, - description, - }, - i - ) => { - return ( - - -
- - {name} - {required ? "*" : null} - - {description && ( - - - - - { - event.preventDefault() - ;(event.currentTarget as HTMLElement)?.focus() - }} - > -

{description}

-
-
- )} -
-
- -
- - {Boolean(typeSimple) ? typeSimple : type} - - {Boolean(typeSimple) && Boolean(type) && ( - - - - - - -
- - {type} - -
-
-
-
- )} -
-
- - - {Boolean(defaultValue) ? ( - - {defaultValue} - - ) : ( - - )} - -
- ) - } - )} -
-
-
- ) -} diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 869343f4ad6..04e06cae50a 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -70,10 +70,6 @@ module.exports = { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, - "collapsible-up": { - from: { height: "var(--radix-collapsible-content-height)" }, - to: { height: "0" }, - }, "caret-blink": { "0%,70%,100%": { opacity: "1" }, "20%,50%": { opacity: "0" }, @@ -83,7 +79,6 @@ module.exports = { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", "caret-blink": "caret-blink 1.25s ease-out infinite", - "collapsible-up": "collapsible-up 0.2s ease-out", }, }, }, From 5aabbea533f10d754bf1b1bfee1a7e3ac4fb467a Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 20 Jan 2025 01:01:39 -0300 Subject: [PATCH 52/62] add .pnpm-store to gitignore file --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 865a3fd3be3..bdd1aa82719 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # dependencies node_modules +.pnpm-store .pnp .pnp.js @@ -38,4 +39,4 @@ tsconfig.tsbuildinfo # ide .idea .fleet -.vscode +.vscode \ No newline at end of file From 067f2d7b0a4e028a8f2d1552da702ecb232c7497 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 20 Jan 2025 01:48:41 -0300 Subject: [PATCH 53/62] chore: add stepperize dependency --- apps/www/package.json | 1 + pnpm-lock.yaml | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/www/package.json b/apps/www/package.json index e05530eef8f..111de6e4b7f 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -52,6 +52,7 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.6", + "@stepperize/react": "^4.1.3", "@tanstack/react-table": "^8.9.1", "@vercel/analytics": "^1.2.2", "@vercel/og": "^0.0.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 402b98dd022..3930e1de35e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,6 +207,9 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.0.6 version: 1.0.6(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@stepperize/react': + specifier: ^4.1.3 + version: 4.1.3(react@18.2.0) '@tanstack/react-table': specifier: ^8.9.1 version: 8.9.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -2762,6 +2765,14 @@ packages: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} engines: {node: '>=6'} + '@stepperize/core@1.0.0': + resolution: {integrity: sha512-DGhUuMvJTo27rE/DizE4junwqR3EGr9JrbdrjhyCHrDwmdYZI1DDLftj24TtHenhPNBKAOpudG6KW5dibnJxcg==} + + '@stepperize/react@4.1.3': + resolution: {integrity: sha512-Qq3oLO6BQ/rUtTvsMG7GY6her0a4Mlev+3wdAzxB8sOPrhbbSsIuq+GUwGviHch0SFmZ0N1DT1DURn0Pm7fUgA==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@swc/helpers@0.5.11': resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==} @@ -9558,6 +9569,13 @@ snapshots: '@sindresorhus/is@0.14.0': {} + '@stepperize/core@1.0.0': {} + + '@stepperize/react@4.1.3(react@18.2.0)': + dependencies: + '@stepperize/core': 1.0.0 + react: 18.2.0 + '@swc/helpers@0.5.11': dependencies: tslib: 2.6.2 @@ -10925,7 +10943,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.16.1 eslint: 8.44.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0))(eslint@8.44.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 @@ -10937,7 +10955,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0))(eslint@8.44.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -10958,7 +10976,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.44.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.44.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.61.0(eslint@8.44.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.44.0))(eslint@8.44.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3 From 17cf071638660eba763f02ca5d3e33c2e7747d8d Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 20 Jan 2025 01:50:25 -0300 Subject: [PATCH 54/62] chore: add new route --- apps/www/config/docs.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/www/config/docs.ts b/apps/www/config/docs.ts index 907bedcc38d..8974102754c 100644 --- a/apps/www/config/docs.ts +++ b/apps/www/config/docs.ts @@ -363,6 +363,12 @@ export const docsConfig: DocsConfig = { href: "/docs/components/sonner", items: [], }, + { + title: "Stepper", + href: "/docs/components/stepper", + items: [], + label: "New", + }, { title: "Switch", href: "/docs/components/switch", From cca4489a21f98c6a5f401f621117605152478ddf Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Tue, 21 Jan 2025 11:11:00 -0300 Subject: [PATCH 55/62] feat: add stepper and stepper-demo components to registry - Introduced a new UI component "stepper" with its dependencies and file path. - Added an example component "stepper-demo" that utilizes the "stepper". - Updated registry files to include both components in the respective JSON and TypeScript files. --- apps/www/__registry__/index.tsx | 60 ++ apps/www/content/docs/components/stepper.mdx | 95 +++ apps/www/public/r/index.json | 20 + .../public/r/styles/default/stepper-demo.json | 17 + apps/www/public/r/styles/default/stepper.json | 24 + .../r/styles/new-york/stepper-demo.json | 17 + .../www/public/r/styles/new-york/stepper.json | 24 + .../default/examples/stepper-demo.tsx | 63 ++ apps/www/registry/default/ui/stepper.tsx | 555 ++++++++++++++++++ .../new-york/examples/stepper-demo.tsx | 62 ++ apps/www/registry/new-york/ui/stepper.tsx | 555 ++++++++++++++++++ apps/www/registry/registry-examples.ts | 11 + apps/www/registry/registry-ui.ts | 17 + 13 files changed, 1520 insertions(+) create mode 100644 apps/www/content/docs/components/stepper.mdx create mode 100644 apps/www/public/r/styles/default/stepper-demo.json create mode 100644 apps/www/public/r/styles/default/stepper.json create mode 100644 apps/www/public/r/styles/new-york/stepper-demo.json create mode 100644 apps/www/public/r/styles/new-york/stepper.json create mode 100644 apps/www/registry/default/examples/stepper-demo.tsx create mode 100644 apps/www/registry/default/ui/stepper.tsx create mode 100644 apps/www/registry/new-york/examples/stepper-demo.tsx create mode 100644 apps/www/registry/new-york/ui/stepper.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index 5b61ac36371..bd8d1eb7de2 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -590,6 +590,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "stepper": { + name: "stepper", + description: "", + type: "registry:ui", + registryDependencies: ["button","separator"], + files: [{ + path: "registry/new-york/ui/stepper.tsx", + type: "registry:ui", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/new-york/ui/stepper.tsx")), + source: "", + meta: undefined, + }, "switch": { name: "switch", description: "", @@ -4524,6 +4539,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "stepper-demo": { + name: "stepper-demo", + description: "", + type: "registry:example", + registryDependencies: ["stepper"], + files: [{ + path: "registry/new-york/examples/stepper-demo.tsx", + type: "registry:example", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/new-york/examples/stepper-demo.tsx")), + source: "", + meta: undefined, + }, "switch-demo": { name: "switch-demo", description: "", @@ -5845,6 +5875,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "stepper": { + name: "stepper", + description: "", + type: "registry:ui", + registryDependencies: ["button","separator"], + files: [{ + path: "registry/default/ui/stepper.tsx", + type: "registry:ui", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/default/ui/stepper.tsx")), + source: "", + meta: undefined, + }, "switch": { name: "switch", description: "", @@ -9779,6 +9824,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "stepper-demo": { + name: "stepper-demo", + description: "", + type: "registry:example", + registryDependencies: ["stepper"], + files: [{ + path: "registry/default/examples/stepper-demo.tsx", + type: "registry:example", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/default/examples/stepper-demo.tsx")), + source: "", + meta: undefined, + }, "switch-demo": { name: "switch-demo", description: "", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx new file mode 100644 index 00000000000..ed35b90016b --- /dev/null +++ b/apps/www/content/docs/components/stepper.mdx @@ -0,0 +1,95 @@ +--- +title: Stepper +description: An opinionated stepper component for React to create step-by-step workflows. +component: true +links: + doc: https://stepperize.com/docs/react + api: https://stepperize.vercel.app/docs/react/api-references/define +--- + + + +## About + +Stepperize is built and maintained by [damianricobelli](https://twitter.com/damianricobelli). + +## Installation + + + + + CLI + Manual + + + +```bash +npx shadcn@latest add stepper +``` + + + + + + + +Install the following dependencies: + +```bash +npm install @stepperize/react +``` + +Copy and paste the following code into your project. + + + +Update the import paths to match your project setup. + + + + + + + +## Structure + +A `Stepper` component is composed of the following parts: + +- `Stepper` - Handles the stepper logic. +- `StepperNavigation` - Contains the buttons and labels to navigate through the steps. +- `StepperStep` - Step component. +- `StepperTitle` - Step title. +- `StepperDescription` - Step description. +- `StepperPanel` - Section to render the step content based on the current step. +- `StepperControls` - Step controls to navigate through the steps. +- `StepperAction` - Next, previous and reset buttons. + +## defineStepper + +The `defineStepper` function is used to define the steps. It returns a `Stepper` instance with a hook and utils to interact with the stepper. + +For example, you can define the steps like this: + +```tsx +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + }, + { + id: "step-2", + title: "Step 2", + }, + { + id: "step-3", + title: "Step 3", + } +) +``` + +Each instance will return: + +- `Scoped` - _This will not need to be used as it is already wrapped in Stepper._ +- `steps` - Array of steps. +- `useStepper` - Hook to interact with the stepper component. +- `utils` - Provides a set of pure functions for working with steps. diff --git a/apps/www/public/r/index.json b/apps/www/public/r/index.json index 590d2f98ca6..78d4e899c6d 100644 --- a/apps/www/public/r/index.json +++ b/apps/www/public/r/index.json @@ -601,6 +601,26 @@ } ] }, + { + "name": "stepper", + "type": "registry:ui", + "dependencies": [ + "@radix-ui/react-slot", + "@stepperize/react", + "class-variance-authority", + "lucide-react" + ], + "registryDependencies": [ + "button", + "separator" + ], + "files": [ + { + "path": "ui/stepper.tsx", + "type": "registry:ui" + } + ] + }, { "name": "switch", "type": "registry:ui", diff --git a/apps/www/public/r/styles/default/stepper-demo.json b/apps/www/public/r/styles/default/stepper-demo.json new file mode 100644 index 00000000000..274014a7e41 --- /dev/null +++ b/apps/www/public/r/styles/default/stepper-demo.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-demo", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-demo.tsx", + "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

Content for {step.id}

\n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
\n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/stepper.json b/apps/www/public/r/styles/default/stepper.json new file mode 100644 index 00000000000..8e2d10edea0 --- /dev/null +++ b/apps/www/public/r/styles/default/stepper.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper", + "type": "registry:ui", + "author": "shadcn (https://ui.shadcn.com)", + "dependencies": [ + "@radix-ui/react-slot", + "@stepperize/react", + "class-variance-authority", + "lucide-react" + ], + "registryDependencies": [ + "button", + "separator" + ], + "files": [ + { + "path": "ui/stepper.tsx", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/registry/default/ui/button\"\nimport { Separator } from \"@/registry/default/ui/separator\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n ...props\n}: StepperConfig & JSX.IntrinsicElements[\"div\"]) {\n const { instance } = props\n\n return (\n \n
\n {children}\n
\n
\n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Checkout Steps\",\n ...props\n}: Omit & {\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
    \n {typeof children === \"function\" ? children({ methods }) : children}\n
\n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & { of: T; icon?: Icon }) => {\n const { instance, variant } = useStepper()\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isCurrent = currentStep?.id === of.id\n\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
\n {title}\n {description}\n
\n \n )\n }\n\n return (\n <>\n
  • \n \n = stepIndex}\n icon={icon}\n />\n
    \n {title}\n {description}\n
    \n \n
  • \n\n {variant === \"horizontal\" && !isLast && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst stepVariants = cva(\n [\n \"stepper-step-button flex shrink-0 items-center gap-2 rounded-md transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-8 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n ],\n {\n variants: {\n variant: {\n horizontal: \"flex-col\",\n vertical: \"flex-row\",\n circle: \"flex-row\",\n },\n },\n }\n)\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"h4\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"p\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\nconst DefaultStepIndicator = ({\n stepIndex,\n fill,\n icon,\n}: {\n stepIndex: number\n fill: boolean\n icon: React.ReactNode\n}) => (\n \n {icon ?? stepIndex + 1}\n \n)\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n
    \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n methods: Stepperize.Stepper\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n \n {typeof children === \"function\"\n ? children({ step: step as T, methods, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", + "type": "registry:ui", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper-demo.json b/apps/www/public/r/styles/new-york/stepper-demo.json new file mode 100644 index 00000000000..d3e89c9ad24 --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-demo.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-demo", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-demo.tsx", + "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper.json b/apps/www/public/r/styles/new-york/stepper.json new file mode 100644 index 00000000000..718334e13d7 --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper", + "type": "registry:ui", + "author": "shadcn (https://ui.shadcn.com)", + "dependencies": [ + "@radix-ui/react-slot", + "@stepperize/react", + "class-variance-authority", + "lucide-react" + ], + "registryDependencies": [ + "button", + "separator" + ], + "files": [ + { + "path": "ui/stepper.tsx", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/registry/new-york/ui/button\"\nimport { Separator } from \"@/registry/new-york/ui/separator\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n ...props\n}: StepperConfig & JSX.IntrinsicElements[\"div\"]) {\n const { instance } = props\n\n return (\n \n
    \n {children}\n
    \n
    \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Checkout Steps\",\n ...props\n}: Omit & {\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & { of: T; icon?: Icon }) => {\n const { instance, variant } = useStepper()\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isCurrent = currentStep?.id === of.id\n\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n
  • \n \n = stepIndex}\n icon={icon}\n />\n
    \n {title}\n {description}\n
    \n \n
  • \n\n {variant === \"horizontal\" && !isLast && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst stepVariants = cva(\n [\n \"stepper-step-button flex shrink-0 items-center gap-2 rounded-md transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-8 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n ],\n {\n variants: {\n variant: {\n horizontal: \"flex-col\",\n vertical: \"flex-row\",\n circle: \"flex-row\",\n },\n },\n }\n)\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"h4\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"p\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\nconst DefaultStepIndicator = ({\n stepIndex,\n fill,\n icon,\n}: {\n stepIndex: number\n fill: boolean\n icon: React.ReactNode\n}) => (\n \n {icon ?? stepIndex + 1}\n \n)\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n
    \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n methods: Stepperize.Stepper\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n \n {typeof children === \"function\"\n ? children({ step: step as T, methods, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", + "type": "registry:ui", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/registry/default/examples/stepper-demo.tsx b/apps/www/registry/default/examples/stepper-demo.tsx new file mode 100644 index 00000000000..19f6535b42f --- /dev/null +++ b/apps/www/registry/default/examples/stepper-demo.tsx @@ -0,0 +1,63 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + }, + { + id: "step-2", + title: "Step 2", + }, + { + id: "step-3", + title: "Step 3", + } +) + +export default function StepperDemo() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx new file mode 100644 index 00000000000..83329bc2086 --- /dev/null +++ b/apps/www/registry/default/ui/stepper.tsx @@ -0,0 +1,555 @@ +"use client" + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import * as Stepperize from "@stepperize/react" +import { cva } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Button, buttonVariants } from "@/registry/default/ui/button" +import { Separator } from "@/registry/default/ui/separator" + +type StepperProviderProps = StepperConfig & { + children: React.ReactNode +} + +type StepperVariant = "horizontal" | "vertical" | "circle" + +type StepperConfig = { + instance: ReturnType> + variant?: StepperVariant +} + +const StepContext = React.createContext>({ + instance: {} as ReturnType>, + variant: "horizontal", +}) + +const StepperProvider = ({ + children, + ...props +}: StepperProviderProps) => { + const Scope = props.instance.Scoped + return ( + + {children} + + ) +} + +const useStepper = (): StepperConfig => { + const context = React.useContext(StepContext) + if (!context) { + throw new Error("useStepper must be used within a Stepper") + } + return context +} + +function Stepper({ + children, + variant = "horizontal", + className, + ...props +}: StepperConfig & JSX.IntrinsicElements["div"]) { + const { instance } = props + + return ( + +
    + {children} +
    +
    + ) +} + +const StepperNavigation = ({ + children, + className, + "aria-label": ariaLabel = "Checkout Steps", + ...props +}: Omit & { + children: + | React.ReactNode + | ((props: { + methods: Stepperize.Stepper + }) => React.ReactNode) +}) => { + const { variant, instance } = useStepper() + + const methods = instance.useStepper() as Stepperize.Stepper + + return ( + + ) +} + +const listVariants = cva("stepper-navigation-list flex gap-2", { + variants: { + variant: { + horizontal: "flex-row items-center justify-between", + vertical: "flex-col", + circle: "flex-row items-center justify-between", + }, + }, +}) + +const StepperStep = ({ + children, + className, + of, + icon, + ...props +}: JSX.IntrinsicElements["button"] & { of: T; icon?: Icon }) => { + const { instance, variant } = useStepper() + const methods = instance.useStepper() as Stepperize.Stepper + + const currentStep = methods.current + + const isLast = instance.utils.getLast().id === of.id + const stepIndex = instance.utils.getIndex(of.id) + const currentIndex = instance.utils.getIndex(currentStep?.id ?? "") + const isCurrent = currentStep?.id === of.id + + const childMap = useStepChildren(children) + + const title = childMap.get("title") + const description = childMap.get("description") + const panel = childMap.get("panel") + + if (variant === "circle") { + return ( +
  • + +
    + {title} + {description} +
    +
  • + ) + } + + return ( + <> +
  • + +
  • + + {variant === "horizontal" && !isLast && ( + + )} + + {variant === "vertical" && ( +
    + {!isLast && ( +
    + +
    + )} +
    {panel}
    +
    + )} + + ) +} + +const stepVariants = cva( + [ + "stepper-step-button flex shrink-0 items-center gap-2 rounded-md transition-colors", + "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-8 focus-visible:ring-offset-background", + "disabled:pointer-events-none disabled:opacity-50", + ], + { + variants: { + variant: { + horizontal: "flex-col", + vertical: "flex-row", + circle: "flex-row", + }, + }, + } +) + +const extractChildren = (children: React.ReactNode) => { + const childrenArray = React.Children.toArray(children) + const map = new Map() + + for (const child of childrenArray) { + if (React.isValidElement(child)) { + if (child.type === StepperTitle) { + map.set("title", child) + } else if (child.type === StepperDescription) { + map.set("description", child) + } else if (child.type === StepperPanel) { + map.set("panel", child) + } + } + } + + return map +} + +const useStepChildren = (children: React.ReactNode) => { + return React.useMemo(() => extractChildren(children), [children]) +} + +const StepperTitle = ({ + children, + className, + asChild, + ...props +}: JSX.IntrinsicElements["h4"] & { asChild?: boolean }) => { + const Comp = asChild ? Slot : "h4" + + return ( + + {children} + + ) +} + +const StepperDescription = ({ + children, + className, + asChild, + ...props +}: JSX.IntrinsicElements["p"] & { asChild?: boolean }) => { + const Comp = asChild ? Slot : "p" + + return ( + + {children} + + ) +} + +const DefaultStepIndicator = ({ + stepIndex, + fill, + icon, +}: { + stepIndex: number + fill: boolean + icon: React.ReactNode +}) => ( + + {icon ?? stepIndex + 1} + +) + +type CircleStepIndicatorProps = { + currentStep: number + totalSteps: number + size?: number + strokeWidth?: number +} + +const CircleStepIndicator = ({ + currentStep, + totalSteps, + size = 80, + strokeWidth = 6, +}: CircleStepIndicatorProps) => { + const radius = (size - strokeWidth) / 2 + const circumference = radius * 2 * Math.PI + const fillPercentage = (currentStep / totalSteps) * 100 + const dashOffset = circumference - (circumference * fillPercentage) / 100 + + return ( +
    + + Step Indicator + + + +
    + + {currentStep} of {totalSteps} + +
    +
    + ) +} + +const StepperPanel = ({ + children, + className, + when, + asChild, + ...props +}: Omit & { + asChild?: boolean + when: T + children: + | React.ReactNode + | ((props: { + step: T + methods: Stepperize.Stepper + onBeforeAction: ( + action: StepAction, + callback: (params: { + prevStep: Stepperize.Step + nextStep: Stepperize.Step + }) => Promise | boolean + ) => void + }) => React.ReactNode) +}) => { + const Comp = asChild ? Slot : "div" + const { instance } = useStepper() + + const methods = instance.useStepper() + + if (instance.utils.getIndex(when.id) === -1) { + throw new Error(`Step ${when.id} does not exist in the stepper instance`) + } + + const onBeforeAction = React.useCallback( + async ( + action: StepAction, + callback: (params: { + prevStep: Stepperize.Step + nextStep: Stepperize.Step + }) => Promise | boolean + ) => { + const prevStep = methods.current + const nextStep = + action === "next" + ? instance.utils.getNext(prevStep.id) + : action === "prev" + ? instance.utils.getPrev(prevStep.id) + : instance.utils.getFirst() + + const shouldProceed = await callback({ prevStep, nextStep }) + if (shouldProceed) { + if (action === "next") methods.next() + if (action === "prev") methods.prev() + if (action === "reset") methods.reset() + } + }, + [methods, instance.utils] + ) + + return ( + <> + {methods.when(when.id, (step) => ( + + {typeof children === "function" + ? children({ step: step as T, methods, onBeforeAction }) + : children} + + ))} + + ) +} + +const StepperControls = ({ + children, + asChild, + className, + ...props +}: Omit & { + asChild?: boolean + children: + | React.ReactNode + | ((props: { + methods: Stepperize.Stepper + }) => React.ReactNode) +}) => { + const Comp = asChild ? Slot : "div" + const { instance } = useStepper() + + const methods = instance.useStepper() + + return ( + + {typeof children === "function" ? children({ methods }) : children} + + ) +} + +type StepAction = "next" | "prev" | "reset" + +type StepperActionProps = { + action: StepAction + children: React.ReactNode + asChild?: boolean + onBeforeAction?: ({ + event, + prevStep, + nextStep, + }: { + event: React.MouseEvent + prevStep: Stepperize.Step + nextStep: Stepperize.Step + }) => Promise | boolean + className?: string +} + +const StepperAction = ({ + action, + children, + asChild = false, + onBeforeAction, + className, + disabled, + ...props +}: JSX.IntrinsicElements["button"] & StepperActionProps) => { + const { instance } = useStepper() + const methods = instance.useStepper() + + const currentStep = methods.current + + const isDisabled = (action: StepAction) => + action === "prev" && methods.isFirst + + const actionMap = React.useMemo( + () => ({ + next: methods.next, + prev: methods.prev, + reset: methods.reset, + }), + [methods] + ) + + const handleClick = React.useCallback( + async (event: React.MouseEvent) => { + if (onBeforeAction) { + const nextStep = + action === "next" + ? instance.utils.getNext(currentStep.id) + : action === "prev" + ? instance.utils.getPrev(currentStep.id) + : instance.utils.getFirst() + const shouldProceed = await onBeforeAction({ + event, + prevStep: currentStep, + nextStep, + }) + if (!shouldProceed) { + return + } + } + + actionMap[action]?.() + }, + [onBeforeAction, actionMap, action, instance.utils, currentStep] + ) + + const Comp = asChild ? Slot : Button + + if ( + (methods.isLast && (action === "next" || action === "prev")) || + (!methods.isLast && action === "reset") + ) { + return null + } + + return ( + + {children} + + ) +} + +const defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper + +export { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} diff --git a/apps/www/registry/new-york/examples/stepper-demo.tsx b/apps/www/registry/new-york/examples/stepper-demo.tsx new file mode 100644 index 00000000000..3fcac2939fa --- /dev/null +++ b/apps/www/registry/new-york/examples/stepper-demo.tsx @@ -0,0 +1,62 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/new-york/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + }, + { + id: "step-2", + title: "Step 2", + }, + { + id: "step-3", + title: "Step 3", + } +) + +export default function StepperDemo() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx new file mode 100644 index 00000000000..6c02be8e0c1 --- /dev/null +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -0,0 +1,555 @@ +"use client" + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import * as Stepperize from "@stepperize/react" +import { cva } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Button, buttonVariants } from "@/registry/new-york/ui/button" +import { Separator } from "@/registry/new-york/ui/separator" + +type StepperProviderProps = StepperConfig & { + children: React.ReactNode +} + +type StepperVariant = "horizontal" | "vertical" | "circle" + +type StepperConfig = { + instance: ReturnType> + variant?: StepperVariant +} + +const StepContext = React.createContext>({ + instance: {} as ReturnType>, + variant: "horizontal", +}) + +const StepperProvider = ({ + children, + ...props +}: StepperProviderProps) => { + const Scope = props.instance.Scoped + return ( + + {children} + + ) +} + +const useStepper = (): StepperConfig => { + const context = React.useContext(StepContext) + if (!context) { + throw new Error("useStepper must be used within a Stepper") + } + return context +} + +function Stepper({ + children, + variant = "horizontal", + className, + ...props +}: StepperConfig & JSX.IntrinsicElements["div"]) { + const { instance } = props + + return ( + +
    + {children} +
    +
    + ) +} + +const StepperNavigation = ({ + children, + className, + "aria-label": ariaLabel = "Checkout Steps", + ...props +}: Omit & { + children: + | React.ReactNode + | ((props: { + methods: Stepperize.Stepper + }) => React.ReactNode) +}) => { + const { variant, instance } = useStepper() + + const methods = instance.useStepper() as Stepperize.Stepper + + return ( + + ) +} + +const listVariants = cva("stepper-navigation-list flex gap-2", { + variants: { + variant: { + horizontal: "flex-row items-center justify-between", + vertical: "flex-col", + circle: "flex-row items-center justify-between", + }, + }, +}) + +const StepperStep = ({ + children, + className, + of, + icon, + ...props +}: JSX.IntrinsicElements["button"] & { of: T; icon?: Icon }) => { + const { instance, variant } = useStepper() + const methods = instance.useStepper() as Stepperize.Stepper + + const currentStep = methods.current + + const isLast = instance.utils.getLast().id === of.id + const stepIndex = instance.utils.getIndex(of.id) + const currentIndex = instance.utils.getIndex(currentStep?.id ?? "") + const isCurrent = currentStep?.id === of.id + + const childMap = useStepChildren(children) + + const title = childMap.get("title") + const description = childMap.get("description") + const panel = childMap.get("panel") + + if (variant === "circle") { + return ( +
  • + +
    + {title} + {description} +
    +
  • + ) + } + + return ( + <> +
  • + +
  • + + {variant === "horizontal" && !isLast && ( + + )} + + {variant === "vertical" && ( +
    + {!isLast && ( +
    + +
    + )} +
    {panel}
    +
    + )} + + ) +} + +const stepVariants = cva( + [ + "stepper-step-button flex shrink-0 items-center gap-2 rounded-md transition-colors", + "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-8 focus-visible:ring-offset-background", + "disabled:pointer-events-none disabled:opacity-50", + ], + { + variants: { + variant: { + horizontal: "flex-col", + vertical: "flex-row", + circle: "flex-row", + }, + }, + } +) + +const extractChildren = (children: React.ReactNode) => { + const childrenArray = React.Children.toArray(children) + const map = new Map() + + for (const child of childrenArray) { + if (React.isValidElement(child)) { + if (child.type === StepperTitle) { + map.set("title", child) + } else if (child.type === StepperDescription) { + map.set("description", child) + } else if (child.type === StepperPanel) { + map.set("panel", child) + } + } + } + + return map +} + +const useStepChildren = (children: React.ReactNode) => { + return React.useMemo(() => extractChildren(children), [children]) +} + +const StepperTitle = ({ + children, + className, + asChild, + ...props +}: JSX.IntrinsicElements["h4"] & { asChild?: boolean }) => { + const Comp = asChild ? Slot : "h4" + + return ( + + {children} + + ) +} + +const StepperDescription = ({ + children, + className, + asChild, + ...props +}: JSX.IntrinsicElements["p"] & { asChild?: boolean }) => { + const Comp = asChild ? Slot : "p" + + return ( + + {children} + + ) +} + +const DefaultStepIndicator = ({ + stepIndex, + fill, + icon, +}: { + stepIndex: number + fill: boolean + icon: React.ReactNode +}) => ( + + {icon ?? stepIndex + 1} + +) + +type CircleStepIndicatorProps = { + currentStep: number + totalSteps: number + size?: number + strokeWidth?: number +} + +const CircleStepIndicator = ({ + currentStep, + totalSteps, + size = 80, + strokeWidth = 6, +}: CircleStepIndicatorProps) => { + const radius = (size - strokeWidth) / 2 + const circumference = radius * 2 * Math.PI + const fillPercentage = (currentStep / totalSteps) * 100 + const dashOffset = circumference - (circumference * fillPercentage) / 100 + + return ( +
    + + Step Indicator + + + +
    + + {currentStep} of {totalSteps} + +
    +
    + ) +} + +const StepperPanel = ({ + children, + className, + when, + asChild, + ...props +}: Omit & { + asChild?: boolean + when: T + children: + | React.ReactNode + | ((props: { + step: T + methods: Stepperize.Stepper + onBeforeAction: ( + action: StepAction, + callback: (params: { + prevStep: Stepperize.Step + nextStep: Stepperize.Step + }) => Promise | boolean + ) => void + }) => React.ReactNode) +}) => { + const Comp = asChild ? Slot : "div" + const { instance } = useStepper() + + const methods = instance.useStepper() + + if (instance.utils.getIndex(when.id) === -1) { + throw new Error(`Step ${when.id} does not exist in the stepper instance`) + } + + const onBeforeAction = React.useCallback( + async ( + action: StepAction, + callback: (params: { + prevStep: Stepperize.Step + nextStep: Stepperize.Step + }) => Promise | boolean + ) => { + const prevStep = methods.current + const nextStep = + action === "next" + ? instance.utils.getNext(prevStep.id) + : action === "prev" + ? instance.utils.getPrev(prevStep.id) + : instance.utils.getFirst() + + const shouldProceed = await callback({ prevStep, nextStep }) + if (shouldProceed) { + if (action === "next") methods.next() + if (action === "prev") methods.prev() + if (action === "reset") methods.reset() + } + }, + [methods, instance.utils] + ) + + return ( + <> + {methods.when(when.id, (step) => ( + + {typeof children === "function" + ? children({ step: step as T, methods, onBeforeAction }) + : children} + + ))} + + ) +} + +const StepperControls = ({ + children, + asChild, + className, + ...props +}: Omit & { + asChild?: boolean + children: + | React.ReactNode + | ((props: { + methods: Stepperize.Stepper + }) => React.ReactNode) +}) => { + const Comp = asChild ? Slot : "div" + const { instance } = useStepper() + + const methods = instance.useStepper() + + return ( + + {typeof children === "function" ? children({ methods }) : children} + + ) +} + +type StepAction = "next" | "prev" | "reset" + +type StepperActionProps = { + action: StepAction + children: React.ReactNode + asChild?: boolean + onBeforeAction?: ({ + event, + prevStep, + nextStep, + }: { + event: React.MouseEvent + prevStep: Stepperize.Step + nextStep: Stepperize.Step + }) => Promise | boolean + className?: string +} + +const StepperAction = ({ + action, + children, + asChild = false, + onBeforeAction, + className, + disabled, + ...props +}: JSX.IntrinsicElements["button"] & StepperActionProps) => { + const { instance } = useStepper() + const methods = instance.useStepper() + + const currentStep = methods.current + + const isDisabled = (action: StepAction) => + action === "prev" && methods.isFirst + + const actionMap = React.useMemo( + () => ({ + next: methods.next, + prev: methods.prev, + reset: methods.reset, + }), + [methods] + ) + + const handleClick = React.useCallback( + async (event: React.MouseEvent) => { + if (onBeforeAction) { + const nextStep = + action === "next" + ? instance.utils.getNext(currentStep.id) + : action === "prev" + ? instance.utils.getPrev(currentStep.id) + : instance.utils.getFirst() + const shouldProceed = await onBeforeAction({ + event, + prevStep: currentStep, + nextStep, + }) + if (!shouldProceed) { + return + } + } + + actionMap[action]?.() + }, + [onBeforeAction, actionMap, action, instance.utils, currentStep] + ) + + const Comp = asChild ? Slot : Button + + if ( + (methods.isLast && (action === "next" || action === "prev")) || + (!methods.isLast && action === "reset") + ) { + return null + } + + return ( + + {children} + + ) +} + +const defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper + +export { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} diff --git a/apps/www/registry/registry-examples.ts b/apps/www/registry/registry-examples.ts index 4f07b12ed65..09f515ae736 100644 --- a/apps/www/registry/registry-examples.ts +++ b/apps/www/registry/registry-examples.ts @@ -1094,6 +1094,17 @@ export const examples: Registry["items"] = [ }, ], }, + { + name: "stepper-demo", + type: "registry:example", + registryDependencies: ["stepper"], + files: [ + { + path: "examples/stepper-demo.tsx", + type: "registry:example", + }, + ], + }, { name: "switch-demo", type: "registry:example", diff --git a/apps/www/registry/registry-ui.ts b/apps/www/registry/registry-ui.ts index a644339f4ee..4c17e489c09 100644 --- a/apps/www/registry/registry-ui.ts +++ b/apps/www/registry/registry-ui.ts @@ -514,6 +514,23 @@ export const ui: Registry["items"] = [ }, ], }, + { + name: "stepper", + type: "registry:ui", + dependencies: [ + "@radix-ui/react-slot", + "@stepperize/react", + "class-variance-authority", + "lucide-react", + ], + registryDependencies: ["button", "separator"], + files: [ + { + path: "ui/stepper.tsx", + type: "registry:ui", + }, + ], + }, { name: "switch", type: "registry:ui", From c699f0ff700518806fc427704243f773842dda82 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Tue, 21 Jan 2025 11:12:43 -0300 Subject: [PATCH 56/62] fix(docs): update stepper documentation link to point to correct URL --- apps/www/content/docs/components/stepper.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index ed35b90016b..1ae03cd9117 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -3,7 +3,7 @@ title: Stepper description: An opinionated stepper component for React to create step-by-step workflows. component: true links: - doc: https://stepperize.com/docs/react + doc: https://stepperize.vercel.app/docs/react api: https://stepperize.vercel.app/docs/react/api-references/define --- From 49585e085e950d1680310057bce71f5d839794f6 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Tue, 21 Jan 2025 11:13:51 -0300 Subject: [PATCH 57/62] fix(docs): update stepper maintainer link to x --- apps/www/content/docs/components/stepper.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 1ae03cd9117..a6ff1ff45b7 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -11,7 +11,7 @@ links: ## About -Stepperize is built and maintained by [damianricobelli](https://twitter.com/damianricobelli). +Stepperize is built and maintained by [damianricobelli](https://x.com/damianricobelli). ## Installation From fe7751af25895a4771d188e2a2a579023edd2d4b Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Fri, 24 Jan 2025 00:08:07 -0300 Subject: [PATCH 58/62] feat: add docs and first example --- apps/www/__registry__/index.tsx | 34 +- apps/www/content/docs/components/stepper.mdx | 350 +++++++++++++++++- apps/www/public/r/index.json | 6 +- .../public/r/styles/default/stepper-demo.json | 2 +- .../r/styles/default/stepper-variants.json | 17 + apps/www/public/r/styles/default/stepper.json | 8 +- .../r/styles/new-york/stepper-demo.json | 2 +- .../r/styles/new-york/stepper-variants.json | 17 + .../www/public/r/styles/new-york/stepper.json | 8 +- .../default/examples/stepper-demo.tsx | 7 +- .../default/examples/stepper-forms.tsx | 68 ++++ .../examples/stepper-label-orientation.tsx | 71 ++++ .../stepper-responsive-orientation.tsx | 69 ++++ .../default/examples/stepper-tracking.tsx | 68 ++++ .../default/examples/stepper-variants.tsx | 176 +++++++++ .../examples/stepper-with-description.tsx | 72 ++++ apps/www/registry/default/ui/stepper.tsx | 211 +++++++---- .../new-york/examples/stepper-demo.tsx | 6 +- .../new-york/examples/stepper-forms.tsx | 68 ++++ .../examples/stepper-label-orientation.tsx | 71 ++++ .../stepper-responsive-orientation.tsx | 69 ++++ .../new-york/examples/stepper-tracking.tsx | 68 ++++ .../new-york/examples/stepper-variants.tsx | 176 +++++++++ .../examples/stepper-with-description.tsx | 72 ++++ apps/www/registry/new-york/ui/stepper.tsx | 211 +++++++---- apps/www/registry/registry-examples.ts | 11 + apps/www/registry/registry-ui.ts | 3 +- 27 files changed, 1758 insertions(+), 183 deletions(-) create mode 100644 apps/www/public/r/styles/default/stepper-variants.json create mode 100644 apps/www/public/r/styles/new-york/stepper-variants.json create mode 100644 apps/www/registry/default/examples/stepper-forms.tsx create mode 100644 apps/www/registry/default/examples/stepper-label-orientation.tsx create mode 100644 apps/www/registry/default/examples/stepper-responsive-orientation.tsx create mode 100644 apps/www/registry/default/examples/stepper-tracking.tsx create mode 100644 apps/www/registry/default/examples/stepper-variants.tsx create mode 100644 apps/www/registry/default/examples/stepper-with-description.tsx create mode 100644 apps/www/registry/new-york/examples/stepper-forms.tsx create mode 100644 apps/www/registry/new-york/examples/stepper-label-orientation.tsx create mode 100644 apps/www/registry/new-york/examples/stepper-responsive-orientation.tsx create mode 100644 apps/www/registry/new-york/examples/stepper-tracking.tsx create mode 100644 apps/www/registry/new-york/examples/stepper-variants.tsx create mode 100644 apps/www/registry/new-york/examples/stepper-with-description.tsx diff --git a/apps/www/__registry__/index.tsx b/apps/www/__registry__/index.tsx index bd8d1eb7de2..ce8a9554bb7 100644 --- a/apps/www/__registry__/index.tsx +++ b/apps/www/__registry__/index.tsx @@ -594,7 +594,7 @@ export const Index: Record = { name: "stepper", description: "", type: "registry:ui", - registryDependencies: ["button","separator"], + registryDependencies: ["button"], files: [{ path: "registry/new-york/ui/stepper.tsx", type: "registry:ui", @@ -4554,6 +4554,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "stepper-variants": { + name: "stepper-variants", + description: "", + type: "registry:example", + registryDependencies: ["stepper"], + files: [{ + path: "registry/new-york/examples/stepper-variants.tsx", + type: "registry:example", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/new-york/examples/stepper-variants.tsx")), + source: "", + meta: undefined, + }, "switch-demo": { name: "switch-demo", description: "", @@ -5879,7 +5894,7 @@ export const Index: Record = { name: "stepper", description: "", type: "registry:ui", - registryDependencies: ["button","separator"], + registryDependencies: ["button"], files: [{ path: "registry/default/ui/stepper.tsx", type: "registry:ui", @@ -9839,6 +9854,21 @@ export const Index: Record = { source: "", meta: undefined, }, + "stepper-variants": { + name: "stepper-variants", + description: "", + type: "registry:example", + registryDependencies: ["stepper"], + files: [{ + path: "registry/default/examples/stepper-variants.tsx", + type: "registry:example", + target: "" + }], + categories: undefined, + component: React.lazy(() => import("@/registry/default/examples/stepper-variants.tsx")), + source: "", + meta: undefined, + }, "switch-demo": { name: "switch-demo", description: "", diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index a6ff1ff45b7..e81f1c40c41 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -64,6 +64,193 @@ A `Stepper` component is composed of the following parts: - `StepperControls` - Step controls to navigate through the steps. - `StepperAction` - Next, previous and reset buttons. +## Usage + +```tsx showLineNumbers +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/components/ui/stepper" + +const stepperInstance = defineStepper( + { id: "step-1", title: "Step 1" }, + { id: "step-2", title: "Step 2" }, + { id: "step-3", title: "Step 3" } +) + +export function Component() { + return ( + + + + + + + ... + + + + + ... + + + ) +} +``` + +## Your first Stepper + +Let's start with the most basic stepper. A stepper with a horizontal navigation. + + + +Create a stepper instance with the `defineStepper` function. + +```tsx +const stepperInstance = defineStepper( + { id: "step-1", title: "Step 1" }, + { id: "step-2", title: "Step 2" }, + { id: "step-3", title: "Step 3" } +) +``` + + + Wrap your application in a `Stepper` component and pass the stepper instance. + + +```tsx +export function MyFirstStepper() { + return ... +} +``` + + + Add a `StepperNavigation` component to render the navigation buttons and + labels. + + +```tsx +export function MyFirstStepper() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + + ) +} +``` + +Add a `StepperPanel` component to render the content of the step. + +```tsx +export function MyFirstStepper() { + const steps = stepperInstance.steps + return ( + + {/* StepperNavigation code */} + {steps.map((step) => ( + + {({ step }) =>

    Content for {step.id}

    } +
    + ))} +
    + ) +} +``` + + + Add a `StepperControls` component to render the buttons to navigate through + the steps. + + +```tsx +export function MyFirstStepper() { + const steps = stepperInstance.steps + return ( + + {/* StepperNavigation code */} + {/* StepperPanel code */} + + Previous + Next + Reset + + + ) +} +``` + +Add some styles to make it look nice. + +```tsx +export function MyFirstStepper() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} +``` + +
    + +## Components + +The components in `stepper.tsx` are built to be composable i.e you build your stepper by putting the provided components together. +They also compose well with other shadcn/ui components such as DropdownMenu, Collapsible or Dialog etc. + +**If you need to change the code in `stepper.tsx`, you are encouraged to do so. The code is yours. Use `stepper.tsx` as a starting point and build your own.** + +In the next sections, we'll go over each component and how to use them. + ## defineStepper The `defineStepper` function is used to define the steps. It returns a `Stepper` instance with a hook and utils to interact with the stepper. @@ -72,18 +259,9 @@ For example, you can define the steps like this: ```tsx const stepperInstance = defineStepper( - { - id: "step-1", - title: "Step 1", - }, - { - id: "step-2", - title: "Step 2", - }, - { - id: "step-3", - title: "Step 3", - } + { id: "step-1", title: "Step 1", description: "Step 1 description" }, + { id: "step-2", title: "Step 2", description: "Step 2 description" }, + { id: "step-3", title: "Step 3", description: "Step 3 description" } ) ``` @@ -93,3 +271,151 @@ Each instance will return: - `steps` - Array of steps. - `useStepper` - Hook to interact with the stepper component. - `utils` - Provides a set of pure functions for working with steps. + + + Each step in the `defineStepper` needs only an `id` to work and they are not + limited to any type. You can define anything within each step, even + components! + + +## Stepper + +The `Stepper` component is used to provide the stepper instance from `defineStepper` to the other components. You should always wrap your application in a `Stepper` component. + +**Props** + +| Name | Type | Description | +| ------------------ | ---------------------------------- | ---------------------------------------------------------------------------------- | +| `instance` | `ReturnType` | Stepper instance. | +| `variant` | `horizontal, vertical or circle` | Style of the stepper. | +| `labelOrientation` | `horizontal, vertical` | Orientation of the labels. This is only applicable if `variant` is `"horizontal"`. | +| `className` | `string` | Class name to apply to the stepper. | + +## StepperNavigation + +The `StepperNavigation` component is used to render the navigation buttons and labels. + +`children` prop is a function that receives the `methods` prop. The `methods` prop is an object with the `useStepper` hook from your stepper instance. + + + If you don't need the `methods` prop, you can just pass the children directly. + + +**Props** + +| Name | Type | Description | +| ---------- | -------------------------------------------------------------------------------- | ------------------- | +| `children` | `React.ReactNode or (methods: ReturnType) => React.ReactNode` | Children to render. | + +## StepperStep + +The `StepperStep` component is a wrapper of the button and labels. You just need to pass the `of` prop which is the step you want to render. + + + This is a good place to add your `onClick` handler. + + +**Props** + +| Name | Type | Description | +| ---- | ------ | --------------- | +| `of` | `Step` | Step to render. | + +### StepperTitle + +The `StepperTitle` component is used to render the title of the step. + +**Props** + +| Name | Type | Description | +| ---------- | ----------------- | ---------------- | +| `children` | `React.ReactNode` | Title to render. | +| `asChild` | `boolean` | Render as child. | + +### StepperDescription + +The `StepperDescription` component is used to render the description of the step. + +**Props** + +| Name | Type | Description | +| ---------- | ----------------- | ---------------------- | +| `children` | `React.ReactNode` | Description to render. | +| `asChild` | `boolean` | Render as child. | + +## StepperPanel + +The `StepperPanel` component is used to render the content of the step. You just need to pass the `when` prop which is the step you want to render. + + + You don't have to worry about the stepper knowing which panel to render. If + the step is not correct, the panel will not be rendered. + + +**Props** + +| Name | Type | Description | +| ---------- | ----------------- | ----------------------------------------- | +| `children` | `React.ReactNode` | Content to render. | +| `when` | `Step` | Used to conditionally render the content. | +| `asChild` | `boolean` | Render as child. | + +## StepperControls + +The `StepperControls` component is used to render the buttons to navigate through the steps. + +**Props** + +| Name | Type | Description | +| ---------- | ----------------- | ------------------ | +| `children` | `React.ReactNode` | Buttons to render. | +| `asChild` | `boolean` | Render as child. | + +### StepperAction + +The `StepperAction` component is used to render the buttons to navigate through the steps. You just need to pass the `action` prop which is the action you want to perform. + + + If you need to execute a function before the button is executed, you can use + the `onBeforeAction` prop. + + +**Props** + +| Name | Type | Description | +| ---------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------- | +| `children` | `React.ReactNode` | Buttons to render. | +| `asChild` | `boolean` | Render as child. | +| `action` | `prev, next or reset` | Action to perform. | +| `onBeforeAction` | `(event: React.MouseEvent, prevStep: Step, nextStep: Step) => boolean` | Function to execute before the action is performed. | + +## Examples + +### Variants + + + +### Responsive variant + +If you need to render the stepper in a responsive way, you can use a custom hook to detect the screen size and render the stepper in a different variant. + +### Description + +### Label Orientation + +### Step tracking + +### Forms + +### Custom components + +If you need to add custom components, you can do so by using the `useStepper` hook and `utils` from your stepper instance. + + + If all this is not enough for you, you can use the + [@stepperize/react](https://stepperize.vercel.app/docs/react) API to create + your own stepper. + diff --git a/apps/www/public/r/index.json b/apps/www/public/r/index.json index 78d4e899c6d..5449cd08d1f 100644 --- a/apps/www/public/r/index.json +++ b/apps/www/public/r/index.json @@ -607,12 +607,10 @@ "dependencies": [ "@radix-ui/react-slot", "@stepperize/react", - "class-variance-authority", - "lucide-react" + "class-variance-authority" ], "registryDependencies": [ - "button", - "separator" + "button" ], "files": [ { diff --git a/apps/www/public/r/styles/default/stepper-demo.json b/apps/www/public/r/styles/default/stepper-demo.json index 274014a7e41..244e8ef7cb1 100644 --- a/apps/www/public/r/styles/default/stepper-demo.json +++ b/apps/www/public/r/styles/default/stepper-demo.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-demo.tsx", - "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", + "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-variants.json b/apps/www/public/r/styles/default/stepper-variants.json new file mode 100644 index 00000000000..ce1975f1ac1 --- /dev/null +++ b/apps/www/public/r/styles/default/stepper-variants.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-variants", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-variants.tsx", + "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/default/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/default/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n \n setVariant(value as \"horizontal\" | \"vertical\" | \"circle\")\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )\n}\n\nconst VerticalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n <>\n methods.goTo(step.id)}\n >\n {step.title}\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n \n \n ))\n }\n
    \n \n Previous\n Next\n Reset\n \n \n )\n}\n\nconst CircleStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) => (\n \n {methods.current.title}\n \n )}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/default/stepper.json b/apps/www/public/r/styles/default/stepper.json index 8e2d10edea0..9ee511324be 100644 --- a/apps/www/public/r/styles/default/stepper.json +++ b/apps/www/public/r/styles/default/stepper.json @@ -6,17 +6,15 @@ "dependencies": [ "@radix-ui/react-slot", "@stepperize/react", - "class-variance-authority", - "lucide-react" + "class-variance-authority" ], "registryDependencies": [ - "button", - "separator" + "button" ], "files": [ { "path": "ui/stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/registry/default/ui/button\"\nimport { Separator } from \"@/registry/default/ui/separator\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n ...props\n}: StepperConfig & JSX.IntrinsicElements[\"div\"]) {\n const { instance } = props\n\n return (\n \n
    \n {children}\n
    \n
    \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Checkout Steps\",\n ...props\n}: Omit & {\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & { of: T; icon?: Icon }) => {\n const { instance, variant } = useStepper()\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isCurrent = currentStep?.id === of.id\n\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n
  • \n \n = stepIndex}\n icon={icon}\n />\n
    \n {title}\n {description}\n
    \n \n
  • \n\n {variant === \"horizontal\" && !isLast && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst stepVariants = cva(\n [\n \"stepper-step-button flex shrink-0 items-center gap-2 rounded-md transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-8 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n ],\n {\n variants: {\n variant: {\n horizontal: \"flex-col\",\n vertical: \"flex-row\",\n circle: \"flex-row\",\n },\n },\n }\n)\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"h4\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"p\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\nconst DefaultStepIndicator = ({\n stepIndex,\n fill,\n icon,\n}: {\n stepIndex: number\n fill: boolean\n icon: React.ReactNode\n}) => (\n \n {icon ?? stepIndex + 1}\n \n)\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n
    \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n methods: Stepperize.Stepper\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n \n {typeof children === \"function\"\n ? children({ step: step as T, methods, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n labelOrientation = \"horizontal\",\n ...props\n}: StepperConfig & React.ComponentProps<\"div\">) {\n const { instance } = props\n\n return (\n \n
    \n {children}\n
    \n \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const id = React.useId()\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n}: {\n isLast: boolean\n} & VariantProps) => {\n if (isLast) return null\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted group-data-[state=completed]:bg-primary group-data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) return\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) return\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) return \"active\"\n if (currentIndex > stepIndex) return \"completed\"\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n methods: Stepperize.Stepper\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n \n {typeof children === \"function\"\n ? children({ step: step as T, methods, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-demo.json b/apps/www/public/r/styles/new-york/stepper-demo.json index d3e89c9ad24..6ec14f5578c 100644 --- a/apps/www/public/r/styles/new-york/stepper-demo.json +++ b/apps/www/public/r/styles/new-york/stepper-demo.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-demo.tsx", - "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", + "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-variants.json b/apps/www/public/r/styles/new-york/stepper-variants.json new file mode 100644 index 00000000000..9a3fb5fa2c7 --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-variants.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-variants", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-variants.tsx", + "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n \n setVariant(value as \"horizontal\" | \"vertical\" | \"circle\")\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )\n}\n\nconst VerticalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n <>\n methods.goTo(step.id)}\n >\n {step.title}\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n \n \n ))\n }\n
    \n \n Previous\n Next\n Reset\n \n \n )\n}\n\nconst CircleStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) => (\n \n {methods.current.title}\n \n )}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper.json b/apps/www/public/r/styles/new-york/stepper.json index 718334e13d7..0f059e689fe 100644 --- a/apps/www/public/r/styles/new-york/stepper.json +++ b/apps/www/public/r/styles/new-york/stepper.json @@ -6,17 +6,15 @@ "dependencies": [ "@radix-ui/react-slot", "@stepperize/react", - "class-variance-authority", - "lucide-react" + "class-variance-authority" ], "registryDependencies": [ - "button", - "separator" + "button" ], "files": [ { "path": "ui/stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button, buttonVariants } from \"@/registry/new-york/ui/button\"\nimport { Separator } from \"@/registry/new-york/ui/separator\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n ...props\n}: StepperConfig & JSX.IntrinsicElements[\"div\"]) {\n const { instance } = props\n\n return (\n \n
    \n {children}\n
    \n
    \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Checkout Steps\",\n ...props\n}: Omit & {\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & { of: T; icon?: Icon }) => {\n const { instance, variant } = useStepper()\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isCurrent = currentStep?.id === of.id\n\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n
  • \n \n = stepIndex}\n icon={icon}\n />\n
    \n {title}\n {description}\n
    \n \n
  • \n\n {variant === \"horizontal\" && !isLast && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst stepVariants = cva(\n [\n \"stepper-step-button flex shrink-0 items-center gap-2 rounded-md transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-8 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n ],\n {\n variants: {\n variant: {\n horizontal: \"flex-col\",\n vertical: \"flex-row\",\n circle: \"flex-row\",\n },\n },\n }\n)\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"h4\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: JSX.IntrinsicElements[\"p\"] & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\nconst DefaultStepIndicator = ({\n stepIndex,\n fill,\n icon,\n}: {\n stepIndex: number\n fill: boolean\n icon: React.ReactNode\n}) => (\n \n {icon ?? stepIndex + 1}\n \n)\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n
    \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n methods: Stepperize.Stepper\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n \n {typeof children === \"function\"\n ? children({ step: step as T, methods, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: JSX.IntrinsicElements[\"button\"] & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n labelOrientation = \"horizontal\",\n ...props\n}: StepperConfig & React.ComponentProps<\"div\">) {\n const { instance } = props\n\n return (\n \n
    \n {children}\n
    \n \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const id = React.useId()\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n}: {\n isLast: boolean\n} & VariantProps) => {\n if (isLast) return null\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted group-data-[state=completed]:bg-primary group-data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) return\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) return\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) return \"active\"\n if (currentIndex > stepIndex) return \"completed\"\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n methods: Stepperize.Stepper\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n \n {typeof children === \"function\"\n ? children({ step: step as T, methods, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/registry/default/examples/stepper-demo.tsx b/apps/www/registry/default/examples/stepper-demo.tsx index 19f6535b42f..eed06489b49 100644 --- a/apps/www/registry/default/examples/stepper-demo.tsx +++ b/apps/www/registry/default/examples/stepper-demo.tsx @@ -2,7 +2,6 @@ import { Stepper, StepperAction, StepperControls, - StepperDescription, StepperNavigation, StepperPanel, StepperStep, @@ -28,7 +27,11 @@ const stepperInstance = defineStepper( export default function StepperDemo() { const steps = stepperInstance.steps return ( - + {({ methods }) => steps.map((step) => ( diff --git a/apps/www/registry/default/examples/stepper-forms.tsx b/apps/www/registry/default/examples/stepper-forms.tsx new file mode 100644 index 00000000000..3c33b87dfd5 --- /dev/null +++ b/apps/www/registry/default/examples/stepper-forms.tsx @@ -0,0 +1,68 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + }, + { + id: "step-2", + title: "Step 2", + }, + { + id: "step-3", + title: "Step 3", + } +) + +export default function StepperDemo() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-label-orientation.tsx b/apps/www/registry/default/examples/stepper-label-orientation.tsx new file mode 100644 index 00000000000..a2992129231 --- /dev/null +++ b/apps/www/registry/default/examples/stepper-label-orientation.tsx @@ -0,0 +1,71 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + description: "This is the first step", + }, + { + id: "step-2", + title: "Step 2", + description: "This is the second step", + }, + { + id: "step-3", + title: "Step 3", + description: "This is the third step", + } +) + +export default function StepperLabelOrientation() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-responsive-orientation.tsx b/apps/www/registry/default/examples/stepper-responsive-orientation.tsx new file mode 100644 index 00000000000..56e3063bdae --- /dev/null +++ b/apps/www/registry/default/examples/stepper-responsive-orientation.tsx @@ -0,0 +1,69 @@ +import { useMediaQuery } from "@/hooks/use-media-query" +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + }, + { + id: "step-2", + title: "Step 2", + }, + { + id: "step-3", + title: "Step 3", + } +) + +export default function StepperResponsiveOrientation() { + const steps = stepperInstance.steps + const isMobile = useMediaQuery("(max-width: 768px)") + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-tracking.tsx b/apps/www/registry/default/examples/stepper-tracking.tsx new file mode 100644 index 00000000000..d0aedd1234f --- /dev/null +++ b/apps/www/registry/default/examples/stepper-tracking.tsx @@ -0,0 +1,68 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + }, + { + id: "step-2", + title: "Step 2", + }, + { + id: "step-3", + title: "Step 3", + } +) + +export default function StepperVerticalFollow() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-variants.tsx b/apps/www/registry/default/examples/stepper-variants.tsx new file mode 100644 index 00000000000..7d0a618fb4f --- /dev/null +++ b/apps/www/registry/default/examples/stepper-variants.tsx @@ -0,0 +1,176 @@ +import * as React from "react" + +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" +import { + Stepper, + StepperAction, + StepperControls, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +type Variant = "horizontal" | "vertical" | "circle" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + }, + { + id: "step-2", + title: "Step 2", + }, + { + id: "step-3", + title: "Step 3", + } +) + +export default function StepperVariants() { + const steps = stepperInstance.steps + + const [variant, setVariant] = React.useState("horizontal") + return ( +
    + + setVariant(value as "horizontal" | "vertical" | "circle") + } + > +
    + + +
    +
    + + +
    +
    + + +
    +
    + {variant === "horizontal" && } + {variant === "vertical" && } + {variant === "circle" && } +
    + ) +} + +const HorizontalStepper = () => { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} + +const VerticalStepper = () => { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + <> + methods.goTo(step.id)} + > + {step.title} + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    +
    + + )) + } +
    + + Previous + Next + Reset + +
    + ) +} + +const CircleStepper = () => { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => ( + + {methods.current.title} + + )} + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-with-description.tsx b/apps/www/registry/default/examples/stepper-with-description.tsx new file mode 100644 index 00000000000..40019860035 --- /dev/null +++ b/apps/www/registry/default/examples/stepper-with-description.tsx @@ -0,0 +1,72 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + description: "This is the first step", + }, + { + id: "step-2", + title: "Step 2", + description: "This is the second step", + }, + { + id: "step-3", + title: "Step 3", + description: "This is the third step", + } +) + +export default function StepperWithDescription() { + const steps = stepperInstance.steps + return ( + + + {({ methods }) => + steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + {step.description} + + )) + } + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + +
    + ) +} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 83329bc2086..3dbe2993ec9 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -3,21 +3,22 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import * as Stepperize from "@stepperize/react" -import { cva } from "class-variance-authority" +import { VariantProps, cva } from "class-variance-authority" import { cn } from "@/lib/utils" -import { Button, buttonVariants } from "@/registry/default/ui/button" -import { Separator } from "@/registry/default/ui/separator" +import { Button } from "@/registry/default/ui/button" type StepperProviderProps = StepperConfig & { children: React.ReactNode } type StepperVariant = "horizontal" | "vertical" | "circle" +type StepperLabelOrientation = "horizontal" | "vertical" type StepperConfig = { instance: ReturnType> variant?: StepperVariant + labelOrientation?: StepperLabelOrientation } const StepContext = React.createContext>({ @@ -49,12 +50,17 @@ function Stepper({ children, variant = "horizontal", className, + labelOrientation = "horizontal", ...props -}: StepperConfig & JSX.IntrinsicElements["div"]) { +}: StepperConfig & React.ComponentProps<"div">) { const { instance } = props return ( - +
    {children}
    @@ -65,9 +71,9 @@ function Stepper({ const StepperNavigation = ({ children, className, - "aria-label": ariaLabel = "Checkout Steps", + "aria-label": ariaLabel = "Stepper Navigation", ...props -}: Omit & { +}: Omit, "children"> & { children: | React.ReactNode | ((props: { @@ -81,6 +87,7 @@ const StepperNavigation = ({ return ( \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const id = React.useId()\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n state,\n disabled,\n}: {\n isLast: boolean\n state: string\n disabled?: boolean\n} & VariantProps) => {\n if (isLast) return null\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted\",\n \"data-[state=completed]:bg-primary data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) return\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) return\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) return \"active\"\n if (currentIndex > stepIndex) return \"completed\"\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance, tracking } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n scrollIntoStepperPanel(node, tracking)}\n {...props}\n >\n {typeof children === \"function\"\n ? children({ step: step as T, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nfunction scrollIntoStepperPanel(\n node: HTMLDivElement | null,\n tracking?: boolean\n) {\n if (tracking) {\n node?.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-demo.json b/apps/www/public/r/styles/new-york/stepper-demo.json index 6ec14f5578c..8732ce9be87 100644 --- a/apps/www/public/r/styles/new-york/stepper-demo.json +++ b/apps/www/public/r/styles/new-york/stepper-demo.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-demo.tsx", - "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", + "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-description.json b/apps/www/public/r/styles/new-york/stepper-description.json new file mode 100644 index 00000000000..5b3d1ba2d5f --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-description.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-description", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-description.tsx", + "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n description: \"This is the first step\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n description: \"This is the second step\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n description: \"This is the third step\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {step.description}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper-form.json b/apps/www/public/r/styles/new-york/stepper-form.json new file mode 100644 index 00000000000..f177415f0e8 --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-form.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-form", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper", + "form" + ], + "files": [ + { + "path": "examples/stepper-form.tsx", + "content": "import { zodResolver } from \"@hookform/resolvers/zod\"\nimport { useForm, useFormContext } from \"react-hook-form\"\nimport { z } from \"zod\"\n\nimport { Form } from \"@/registry/new-york/ui/form\"\nimport { Input } from \"@/registry/new-york/ui/input\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst shippingSchema = z.object({\n address: z.string().min(1, \"Address is required\"),\n city: z.string().min(1, \"City is required\"),\n postalCode: z.string().min(5, \"Postal code is required\"),\n})\n\nconst paymentSchema = z.object({\n cardNumber: z.string().min(16, \"Card number is required\"),\n expirationDate: z.string().min(5, \"Expiration date is required\"),\n cvv: z.string().min(3, \"CVV is required\"),\n})\n\ntype ShippingFormValues = z.infer\ntype PaymentFormValues = z.infer\n\nconst stepperInstance = defineStepper(\n {\n id: \"shipping\",\n title: \"Shipping\",\n schema: shippingSchema,\n },\n {\n id: \"payment\",\n title: \"Payment\",\n schema: paymentSchema,\n },\n {\n id: \"complete\",\n title: \"Complete\",\n schema: z.object({}),\n }\n)\n\nexport default function StepperForm() {\n return (\n \n \n \n )\n}\n\nconst FormStepperComponent = () => {\n const { steps, useStepper, utils } = stepperInstance\n const methods = useStepper()\n\n const form = useForm({\n mode: \"onTouched\",\n resolver: zodResolver(methods.current.schema),\n })\n\n const onSubmit = (values: z.infer) => {\n console.log(`Form values for step ${methods.current.id}:`, values)\n }\n\n const currentIndex = utils.getIndex(methods.current.id)\n\n return (\n
    \n \n \n {steps.map((step) => (\n {\n const valid = await form.trigger()\n if (!valid) return\n if (utils.getIndex(step.id) - currentIndex > 1) return\n methods.goTo(step.id)\n }}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n shipping: () => ,\n payment: () => ,\n complete: () => ,\n })}\n \n Previous\n {\n const valid = await form.trigger()\n if (!valid) return false\n if (utils.getIndex(nextStep.id as any) - currentIndex > 1)\n return false\n return true\n }}\n >\n Next\n \n Reset\n \n \n \n )\n}\n\nconst ShippingForm = () => {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Address\n \n \n {errors.address && (\n \n {errors.address.message}\n \n )}\n
    \n
    \n \n City\n \n \n {errors.city && (\n \n {errors.city.message}\n \n )}\n
    \n
    \n \n Postal Code\n \n \n {errors.postalCode && (\n \n {errors.postalCode.message}\n \n )}\n
    \n
    \n )\n}\n\nfunction PaymentForm() {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Card Number\n \n \n {errors.cardNumber && (\n \n {errors.cardNumber.message}\n \n )}\n
    \n
    \n \n Expiration Date\n \n \n {errors.expirationDate && (\n \n {errors.expirationDate.message}\n \n )}\n
    \n
    \n \n CVV\n \n \n {errors.cvv && (\n {errors.cvv.message}\n )}\n
    \n
    \n )\n}\n\nfunction CompleteComponent() {\n return
    Thank you! Your order is complete.
    \n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper-forms.json b/apps/www/public/r/styles/new-york/stepper-forms.json new file mode 100644 index 00000000000..cb1392076a1 --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-forms.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-forms", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper", + "form" + ], + "files": [ + { + "path": "examples/stepper-forms.tsx", + "content": "import { zodResolver } from \"@hookform/resolvers/zod\"\nimport { Form, useForm, useFormContext } from \"react-hook-form\"\nimport { z } from \"zod\"\n\nimport { Input } from \"@/registry/new-york/ui/input\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst shippingSchema = z.object({\n address: z.string().min(1, \"Address is required\"),\n city: z.string().min(1, \"City is required\"),\n postalCode: z.string().min(5, \"Postal code is required\"),\n})\n\nconst paymentSchema = z.object({\n cardNumber: z.string().min(16, \"Card number is required\"),\n expirationDate: z.string().min(5, \"Expiration date is required\"),\n cvv: z.string().min(3, \"CVV is required\"),\n})\n\ntype ShippingFormValues = z.infer\ntype PaymentFormValues = z.infer\n\nconst ShippingForm = () => {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Address\n \n \n {errors.address && (\n \n {errors.address.message}\n \n )}\n
    \n
    \n \n City\n \n \n {errors.city && (\n \n {errors.city.message}\n \n )}\n
    \n
    \n \n Postal Code\n \n \n {errors.postalCode && (\n \n {errors.postalCode.message}\n \n )}\n
    \n
    \n )\n}\n\nfunction PaymentForm() {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Card Number\n \n \n {errors.cardNumber && (\n \n {errors.cardNumber.message}\n \n )}\n
    \n
    \n \n Expiration Date\n \n \n {errors.expirationDate && (\n \n {errors.expirationDate.message}\n \n )}\n
    \n
    \n \n CVV\n \n \n {errors.cvv && (\n {errors.cvv.message}\n )}\n
    \n
    \n )\n}\n\nfunction CompleteComponent() {\n return
    Thank you! Your order is complete.
    \n}\n\nconst stepperInstance = defineStepper(\n {\n id: \"shipping\",\n title: \"Shipping\",\n schema: shippingSchema,\n component: ShippingForm,\n },\n {\n id: \"payment\",\n title: \"Payment\",\n schema: paymentSchema,\n component: PaymentForm,\n },\n {\n id: \"complete\",\n title: \"Complete\",\n schema: z.object({}),\n component: CompleteComponent,\n }\n)\n\nexport default function StepperDemo() {\n return (\n \n \n \n )\n}\n\nconst FormStepperComponent = () => {\n const { steps, useStepper, utils } = stepperInstance\n const methods = useStepper()\n\n const form = useForm({\n mode: \"onTouched\",\n resolver: zodResolver(methods.current.schema),\n })\n\n const onSubmit = (values: z.infer) => {\n console.log(`Form values for step ${methods.current.id}:`, values)\n if (methods.isLast) {\n methods.reset()\n } else {\n methods.next()\n }\n }\n\n const currentIndex = utils.getIndex(methods.current.id)\n\n return (\n
    \n \n \n {steps.map((step) => (\n {\n const valid = await form.trigger()\n //must be validated\n if (!valid) return\n //can't skip steps forwards but can go back anywhere if validated\n if (utils.getIndex(step.id) - currentIndex > 1) return\n methods.goTo(step.id)\n }}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n \n \n ))}\n \n Previous\n Next\n Reset\n \n \n \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper-icon.json b/apps/www/public/r/styles/new-york/stepper-icon.json new file mode 100644 index 00000000000..e9573a8f65e --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-icon.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-icon", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-icon.tsx", + "content": "import { HomeIcon, SettingsIcon, UserIcon } from \"lucide-react\"\n\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n icon: ,\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n icon: ,\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n icon: ,\n }\n)\n\nexport default function StepperIcon() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n icon={step.icon}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper-label-orientation.json b/apps/www/public/r/styles/new-york/stepper-label-orientation.json new file mode 100644 index 00000000000..c6e985863df --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-label-orientation.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-label-orientation", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-label-orientation.tsx", + "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype LabelOrientation = \"horizontal\" | \"vertical\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [labelOrientation, setLabelOrientation] =\n React.useState(\"horizontal\")\n return (\n
    \n \n setLabelOrientation(value as LabelOrientation)\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n
    \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper-responsive-variant.json b/apps/www/public/r/styles/new-york/stepper-responsive-variant.json index e190904ba82..0b858184023 100644 --- a/apps/www/public/r/styles/new-york/stepper-responsive-variant.json +++ b/apps/www/public/r/styles/new-york/stepper-responsive-variant.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-responsive-variant.tsx", - "content": "import { useMediaQuery } from \"@/hooks/use-media-query\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperResponsiveVariant() {\n const steps = stepperInstance.steps\n const isMobile = useMediaQuery(\"(max-width: 768px)\")\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n <>\n methods.goTo(step.id)}\n >\n {step.title}\n {isMobile && (\n \n {({ step }) => (\n

    \n Content for {step.id}\n

    \n )}\n \n )}\n \n \n ))\n }\n
    \n {!isMobile &&\n steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )\n}\n", + "content": "import { useMediaQuery } from \"@/hooks/use-media-query\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperResponsiveVariant() {\n const steps = stepperInstance.steps\n const isMobile = useMediaQuery(\"(max-width: 768px)\")\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {isMobile && (\n \n {({ step }) => (\n

    \n Content for {step.id}\n

    \n )}\n \n )}\n \n ))}\n
    \n {!isMobile &&\n steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-tracking.json b/apps/www/public/r/styles/new-york/stepper-tracking.json new file mode 100644 index 00000000000..1b8733dc0ed --- /dev/null +++ b/apps/www/public/r/styles/new-york/stepper-tracking.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "stepper-tracking", + "type": "registry:example", + "author": "shadcn (https://ui.shadcn.com)", + "registryDependencies": [ + "stepper" + ], + "files": [ + { + "path": "examples/stepper-tracking.tsx", + "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n },\n {\n id: \"step-4\",\n title: \"Step 4\",\n },\n {\n id: \"step-5\",\n title: \"Step 5\",\n },\n {\n id: \"step-6\",\n title: \"Step 6\",\n }\n)\n\nexport default function StepperVerticalFollow() {\n const steps = stepperInstance.steps\n\n const [tracking, setTracking] = React.useState(false)\n return (\n
    \n setTracking(value === \"true\")}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n
    \n

    \n Content for {step.id}\n

    \n
    \n \n Previous\n Next\n Reset\n \n
    \n \n ))}\n
    \n \n )}\n \n
    \n )\n}\n", + "type": "registry:example", + "target": "" + } + ] +} \ No newline at end of file diff --git a/apps/www/public/r/styles/new-york/stepper-variants.json b/apps/www/public/r/styles/new-york/stepper-variants.json index 9a3fb5fa2c7..fa53f00b7ee 100644 --- a/apps/www/public/r/styles/new-york/stepper-variants.json +++ b/apps/www/public/r/styles/new-york/stepper-variants.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-variants.tsx", - "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n \n setVariant(value as \"horizontal\" | \"vertical\" | \"circle\")\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))\n }\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )\n}\n\nconst VerticalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) =>\n steps.map((step) => (\n <>\n methods.goTo(step.id)}\n >\n {step.title}\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n \n \n ))\n }\n
    \n \n Previous\n Next\n Reset\n \n \n )\n}\n\nconst CircleStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n \n {({ methods }) => (\n \n {methods.current.title}\n \n )}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n
    \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n setVariant(value as Variant)}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n\nconst VerticalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n \n ))}\n
    \n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n\nconst CircleStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n \n {methods.current.title}\n \n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper.json b/apps/www/public/r/styles/new-york/stepper.json index 0f059e689fe..a1ba04d15ee 100644 --- a/apps/www/public/r/styles/new-york/stepper.json +++ b/apps/www/public/r/styles/new-york/stepper.json @@ -14,7 +14,7 @@ "files": [ { "path": "ui/stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n labelOrientation = \"horizontal\",\n ...props\n}: StepperConfig & React.ComponentProps<\"div\">) {\n const { instance } = props\n\n return (\n \n
    \n {children}\n
    \n \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const id = React.useId()\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n}: {\n isLast: boolean\n} & VariantProps) => {\n if (isLast) return null\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted group-data-[state=completed]:bg-primary group-data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) return\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) return\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) return \"active\"\n if (currentIndex > stepIndex) return \"completed\"\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n methods: Stepperize.Stepper\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n \n {typeof children === \"function\"\n ? children({ step: step as T, methods, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n tracking?: boolean\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n labelOrientation = \"horizontal\",\n tracking = false,\n ...props\n}: StepperConfig &\n Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: { methods: Stepperize.Stepper }) => React.ReactNode)\n }) {\n const { instance } = props\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
    \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children: React.ReactNode\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      {children}
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const id = React.useId()\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n state,\n disabled,\n}: {\n isLast: boolean\n state: string\n disabled?: boolean\n} & VariantProps) => {\n if (isLast) return null\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted\",\n \"data-[state=completed]:bg-primary data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) return\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) return\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) return \"active\"\n if (currentIndex > stepIndex) return \"completed\"\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance, tracking } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n scrollIntoStepperPanel(node, tracking)}\n {...props}\n >\n {typeof children === \"function\"\n ? children({ step: step as T, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nfunction scrollIntoStepperPanel(\n node: HTMLDivElement | null,\n tracking?: boolean\n) {\n if (tracking) {\n node?.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/registry/default/examples/stepper-demo.tsx b/apps/www/registry/default/examples/stepper-demo.tsx index eed06489b49..d77ef0677c3 100644 --- a/apps/www/registry/default/examples/stepper-demo.tsx +++ b/apps/www/registry/default/examples/stepper-demo.tsx @@ -32,35 +32,37 @@ export default function StepperDemo() { className="space-y-4" variant="horizontal" > - - {({ methods }) => - steps.map((step) => ( - ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + ))} + + {steps.map((step) => ( + methods.goTo(step.id)} + when={step} + className="h-[200px] content-center rounded border bg-slate-50 p-8" > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ step }) => ( +

    Content for {step.id}

    + )} + + ))} + + Previous + Next + Reset + + + )} ) } diff --git a/apps/www/registry/default/examples/stepper-description.tsx b/apps/www/registry/default/examples/stepper-description.tsx new file mode 100644 index 00000000000..5bb54592c7b --- /dev/null +++ b/apps/www/registry/default/examples/stepper-description.tsx @@ -0,0 +1,73 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + description: "This is the first step", + }, + { + id: "step-2", + title: "Step 2", + description: "This is the second step", + }, + { + id: "step-3", + title: "Step 3", + description: "This is the third step", + } +) + +export default function StepperDemo() { + const steps = stepperInstance.steps + return ( + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + {step.description} + + ))} + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )} +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-form.tsx b/apps/www/registry/default/examples/stepper-form.tsx new file mode 100644 index 00000000000..a5c3184d98a --- /dev/null +++ b/apps/www/registry/default/examples/stepper-form.tsx @@ -0,0 +1,251 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm, useFormContext } from "react-hook-form" +import { z } from "zod" + +import { Form } from "@/registry/default/ui/form" +import { Input } from "@/registry/default/ui/input" +import { + Stepper, + StepperAction, + StepperControls, + StepperNavigation, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const shippingSchema = z.object({ + address: z.string().min(1, "Address is required"), + city: z.string().min(1, "City is required"), + postalCode: z.string().min(5, "Postal code is required"), +}) + +const paymentSchema = z.object({ + cardNumber: z.string().min(16, "Card number is required"), + expirationDate: z.string().min(5, "Expiration date is required"), + cvv: z.string().min(3, "CVV is required"), +}) + +type ShippingFormValues = z.infer +type PaymentFormValues = z.infer + +const stepperInstance = defineStepper( + { + id: "shipping", + title: "Shipping", + schema: shippingSchema, + }, + { + id: "payment", + title: "Payment", + schema: paymentSchema, + }, + { + id: "complete", + title: "Complete", + schema: z.object({}), + } +) + +export default function StepperForm() { + return ( + + + + ) +} + +const FormStepperComponent = () => { + const { steps, useStepper, utils } = stepperInstance + const methods = useStepper() + + const form = useForm({ + mode: "onTouched", + resolver: zodResolver(methods.current.schema), + }) + + const onSubmit = (values: z.infer) => { + console.log(`Form values for step ${methods.current.id}:`, values) + } + + const currentIndex = utils.getIndex(methods.current.id) + + return ( +
    + + + {steps.map((step) => ( + { + const valid = await form.trigger() + if (!valid) return + if (utils.getIndex(step.id) - currentIndex > 1) return + methods.goTo(step.id) + }} + > + {step.title} + + ))} + + {methods.switch({ + shipping: () => , + payment: () => , + complete: () => , + })} + + Previous + { + const valid = await form.trigger() + if (!valid) return false + if (utils.getIndex(nextStep.id as any) - currentIndex > 1) + return false + return true + }} + > + Next + + Reset + + + + ) +} + +const ShippingForm = () => { + const { + register, + formState: { errors }, + } = useFormContext() + + return ( +
    +
    + + + {errors.address && ( + + {errors.address.message} + + )} +
    +
    + + + {errors.city && ( + + {errors.city.message} + + )} +
    +
    + + + {errors.postalCode && ( + + {errors.postalCode.message} + + )} +
    +
    + ) +} + +function PaymentForm() { + const { + register, + formState: { errors }, + } = useFormContext() + + return ( +
    +
    + + + {errors.cardNumber && ( + + {errors.cardNumber.message} + + )} +
    +
    + + + {errors.expirationDate && ( + + {errors.expirationDate.message} + + )} +
    +
    + + + {errors.cvv && ( + {errors.cvv.message} + )} +
    +
    + ) +} + +function CompleteComponent() { + return
    Thank you! Your order is complete.
    +} diff --git a/apps/www/registry/default/examples/stepper-forms.tsx b/apps/www/registry/default/examples/stepper-forms.tsx deleted file mode 100644 index 3c33b87dfd5..00000000000 --- a/apps/www/registry/default/examples/stepper-forms.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - Stepper, - StepperAction, - StepperControls, - StepperDescription, - StepperNavigation, - StepperPanel, - StepperStep, - StepperTitle, - defineStepper, -} from "@/registry/default/ui/stepper" - -const stepperInstance = defineStepper( - { - id: "step-1", - title: "Step 1", - }, - { - id: "step-2", - title: "Step 2", - }, - { - id: "step-3", - title: "Step 3", - } -) - -export default function StepperDemo() { - const steps = stepperInstance.steps - return ( - - - {({ methods }) => - steps.map((step) => ( - methods.goTo(step.id)} - > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - -
    - ) -} diff --git a/apps/www/registry/default/examples/stepper-icon.tsx b/apps/www/registry/default/examples/stepper-icon.tsx new file mode 100644 index 00000000000..45caf09fb9e --- /dev/null +++ b/apps/www/registry/default/examples/stepper-icon.tsx @@ -0,0 +1,74 @@ +import { HomeIcon, SettingsIcon, UserIcon } from "lucide-react" + +import { + Stepper, + StepperAction, + StepperControls, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/default/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + icon: , + }, + { + id: "step-2", + title: "Step 2", + icon: , + }, + { + id: "step-3", + title: "Step 3", + icon: , + } +) + +export default function StepperIcon() { + const steps = stepperInstance.steps + return ( + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + icon={step.icon} + > + {step.title} + + ))} + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )} +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-label-orientation.tsx b/apps/www/registry/default/examples/stepper-label-orientation.tsx index a2992129231..136a02d15d4 100644 --- a/apps/www/registry/default/examples/stepper-label-orientation.tsx +++ b/apps/www/registry/default/examples/stepper-label-orientation.tsx @@ -1,8 +1,11 @@ +import * as React from "react" + +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" import { Stepper, StepperAction, StepperControls, - StepperDescription, StepperNavigation, StepperPanel, StepperStep, @@ -10,62 +13,83 @@ import { defineStepper, } from "@/registry/default/ui/stepper" +type LabelOrientation = "horizontal" | "vertical" + const stepperInstance = defineStepper( { id: "step-1", title: "Step 1", - description: "This is the first step", }, { id: "step-2", title: "Step 2", - description: "This is the second step", }, { id: "step-3", title: "Step 3", - description: "This is the third step", } ) -export default function StepperLabelOrientation() { +export default function StepperVariants() { const steps = stepperInstance.steps + + const [labelOrientation, setLabelOrientation] = + React.useState("horizontal") return ( - - - {({ methods }) => - steps.map((step) => ( - methods.goTo(step.id)} - > - {step.title} - - )) +
    + + setLabelOrientation(value as LabelOrientation) } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - - + > +
    + + +
    +
    + + +
    +
    + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + ))} + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )} +
    +
    ) } diff --git a/apps/www/registry/default/examples/stepper-responsive-variant.tsx b/apps/www/registry/default/examples/stepper-responsive-variant.tsx index 6593235797b..85fb106db93 100644 --- a/apps/www/registry/default/examples/stepper-responsive-variant.tsx +++ b/apps/www/registry/default/examples/stepper-responsive-variant.tsx @@ -34,10 +34,10 @@ export default function StepperResponsiveVariant() { className="space-y-4" variant={isMobile ? "vertical" : "horizontal"} > - - {({ methods }) => - steps.map((step) => ( - <> + {({ methods }) => ( + <> + + {steps.map((step) => ( )} - - )) - } - - {!isMobile && - steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + ))} +
    + {!isMobile && + steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )}
    ) } diff --git a/apps/www/registry/default/examples/stepper-tracking.tsx b/apps/www/registry/default/examples/stepper-tracking.tsx index d0aedd1234f..9f8a550e06f 100644 --- a/apps/www/registry/default/examples/stepper-tracking.tsx +++ b/apps/www/registry/default/examples/stepper-tracking.tsx @@ -1,8 +1,11 @@ +import * as React from "react" + +import { Label } from "@/registry/default/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" import { Stepper, StepperAction, StepperControls, - StepperDescription, StepperNavigation, StepperPanel, StepperStep, @@ -22,47 +25,74 @@ const stepperInstance = defineStepper( { id: "step-3", title: "Step 3", + }, + { + id: "step-4", + title: "Step 4", + }, + { + id: "step-5", + title: "Step 5", + }, + { + id: "step-6", + title: "Step 6", } ) export default function StepperVerticalFollow() { const steps = stepperInstance.steps + + const [tracking, setTracking] = React.useState(false) return ( - - - {({ methods }) => - steps.map((step) => ( - methods.goTo(step.id)} - > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - -
    +
    + setTracking(value === "true")} + > +
    + + +
    +
    + + +
    +
    + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + +
    +

    + Content for {step.id} +

    +
    + + Previous + Next + Reset + +
    +
    + ))} +
    + + )} +
    +
    ) } diff --git a/apps/www/registry/default/examples/stepper-variants.tsx b/apps/www/registry/default/examples/stepper-variants.tsx index 7d0a618fb4f..f8aaef2a02d 100644 --- a/apps/www/registry/default/examples/stepper-variants.tsx +++ b/apps/www/registry/default/examples/stepper-variants.tsx @@ -38,21 +38,19 @@ export default function StepperVariants() {
    - setVariant(value as "horizontal" | "vertical" | "circle") - } + onValueChange={(value) => setVariant(value as Variant)} >
    - - + +
    - - + +
    - - + +
    {variant === "horizontal" && } @@ -70,35 +68,37 @@ const HorizontalStepper = () => { className="space-y-4" variant="horizontal" > - - {({ methods }) => - steps.map((step) => ( - ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + ))} + + {steps.map((step) => ( + methods.goTo(step.id)} + when={step} + className="h-[200px] content-center rounded border bg-slate-50 p-8" > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ step }) => ( +

    Content for {step.id}

    + )} + + ))} + + Previous + Next + Reset + + + )} ) } @@ -111,10 +111,10 @@ const VerticalStepper = () => { className="space-y-4" variant="vertical" > - - {({ methods }) => - steps.map((step) => ( - <> + {({ methods }) => ( + <> + + {steps.map((step) => ( { )} - - )) - } - - - Previous - Next - Reset - + ))} + + + Previous + Next + Reset + + + )} ) } @@ -148,29 +148,31 @@ const CircleStepper = () => { const steps = stepperInstance.steps return ( - - {({ methods }) => ( - - {methods.current.title} - - )} - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ methods }) => ( + <> + + + {methods.current.title} + + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )}
    ) } diff --git a/apps/www/registry/default/examples/stepper-with-description.tsx b/apps/www/registry/default/examples/stepper-with-description.tsx index 40019860035..5b50b9d014a 100644 --- a/apps/www/registry/default/examples/stepper-with-description.tsx +++ b/apps/www/registry/default/examples/stepper-with-description.tsx @@ -37,36 +37,38 @@ export default function StepperWithDescription() { variant="vertical" labelOrientation="horizontal" > - - {({ methods }) => - steps.map((step) => ( - ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + {step.description} + + ))} + + {steps.map((step) => ( + methods.goTo(step.id)} + when={step} + className="h-[200px] content-center rounded border bg-slate-50 p-8" > - {step.title} - {step.description} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ step }) => ( +

    Content for {step.id}

    + )} + + ))} + + Previous + Next + Reset + + + )} ) } diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 3dbe2993ec9..77ebbc599d7 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -19,6 +19,7 @@ type StepperConfig = { instance: ReturnType> variant?: StepperVariant labelOrientation?: StepperLabelOrientation + tracking?: boolean } const StepContext = React.createContext>({ @@ -51,18 +52,27 @@ function Stepper({ variant = "horizontal", className, labelOrientation = "horizontal", + tracking = false, ...props -}: StepperConfig & React.ComponentProps<"div">) { +}: StepperConfig & + Omit, "children"> & { + children: + | React.ReactNode + | ((props: { methods: Stepperize.Stepper }) => React.ReactNode) + }) { const { instance } = props + const methods = instance.useStepper() as Stepperize.Stepper + return (
    - {children} + {typeof children === "function" ? children({ methods }) : children}
    ) @@ -74,11 +84,7 @@ const StepperNavigation = ({ "aria-label": ariaLabel = "Stepper Navigation", ...props }: Omit, "children"> & { - children: - | React.ReactNode - | ((props: { - methods: Stepperize.Stepper - }) => React.ReactNode) + children: React.ReactNode }) => { const { variant, instance } = useStepper() @@ -91,9 +97,7 @@ const StepperNavigation = ({ className={cn("stepper-navigation", className)} {...props} > -
      - {typeof children === "function" ? children({ methods }) : children} -
    +
      {children}
    ) } @@ -160,7 +164,7 @@ const StepperStep = ({
  • ({ orientation="horizontal" labelOrientation={labelOrientation} isLast={isLast} + state={dataState} + disabled={props.disabled} /> )}
    @@ -209,14 +215,24 @@ const StepperStep = ({
  • {variant === "horizontal" && labelOrientation === "horizontal" && ( - + )} {variant === "vertical" && (
    {!isLast && (
    - +
    )}
    {panel}
    @@ -230,13 +246,19 @@ const StepperSeparator = ({ orientation, isLast, labelOrientation, + state, + disabled, }: { isLast: boolean + state: string + disabled?: boolean } & VariantProps) => { if (isLast) return null return (
    @@ -245,7 +267,8 @@ const StepperSeparator = ({ const classForSeparator = cva( [ - "bg-muted group-data-[state=completed]:bg-primary group-data-[disabled]:opacity-50", + "bg-muted", + "data-[state=completed]:bg-primary data-[disabled]:opacity-50", "transition-all duration-300 ease-in-out", ], { @@ -427,7 +450,6 @@ const StepperPanel = ({ | React.ReactNode | ((props: { step: T - methods: Stepperize.Stepper onBeforeAction: ( action: StepAction, callback: (params: { @@ -438,7 +460,7 @@ const StepperPanel = ({ }) => React.ReactNode) }) => { const Comp = asChild ? Slot : "div" - const { instance } = useStepper() + const { instance, tracking } = useStepper() const methods = instance.useStepper() @@ -475,9 +497,13 @@ const StepperPanel = ({ return ( <> {methods.when(when.id, (step) => ( - + scrollIntoStepperPanel(node, tracking)} + {...props} + > {typeof children === "function" - ? children({ step: step as T, methods, onBeforeAction }) + ? children({ step: step as T, onBeforeAction }) : children} ))} @@ -485,6 +511,15 @@ const StepperPanel = ({ ) } +function scrollIntoStepperPanel( + node: HTMLDivElement | null, + tracking?: boolean +) { + if (tracking) { + node?.scrollIntoView({ behavior: "smooth", block: "center" }) + } +} + const StepperControls = ({ children, asChild, diff --git a/apps/www/registry/new-york/examples/stepper-demo.tsx b/apps/www/registry/new-york/examples/stepper-demo.tsx index 4783ebca272..53b295e8235 100644 --- a/apps/www/registry/new-york/examples/stepper-demo.tsx +++ b/apps/www/registry/new-york/examples/stepper-demo.tsx @@ -32,35 +32,37 @@ export default function StepperDemo() { className="space-y-4" variant="horizontal" > - - {({ methods }) => - steps.map((step) => ( - ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + ))} + + {steps.map((step) => ( + methods.goTo(step.id)} + when={step} + className="h-[200px] content-center rounded border bg-slate-50 p-8" > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ step }) => ( +

    Content for {step.id}

    + )} + + ))} + + Previous + Next + Reset + + + )} ) } diff --git a/apps/www/registry/new-york/examples/stepper-description.tsx b/apps/www/registry/new-york/examples/stepper-description.tsx new file mode 100644 index 00000000000..40670e465a7 --- /dev/null +++ b/apps/www/registry/new-york/examples/stepper-description.tsx @@ -0,0 +1,73 @@ +import { + Stepper, + StepperAction, + StepperControls, + StepperDescription, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/new-york/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + description: "This is the first step", + }, + { + id: "step-2", + title: "Step 2", + description: "This is the second step", + }, + { + id: "step-3", + title: "Step 3", + description: "This is the third step", + } +) + +export default function StepperDemo() { + const steps = stepperInstance.steps + return ( + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + {step.description} + + ))} + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )} +
    + ) +} diff --git a/apps/www/registry/new-york/examples/stepper-form.tsx b/apps/www/registry/new-york/examples/stepper-form.tsx new file mode 100644 index 00000000000..6060d900ef0 --- /dev/null +++ b/apps/www/registry/new-york/examples/stepper-form.tsx @@ -0,0 +1,251 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm, useFormContext } from "react-hook-form" +import { z } from "zod" + +import { Form } from "@/registry/new-york/ui/form" +import { Input } from "@/registry/new-york/ui/input" +import { + Stepper, + StepperAction, + StepperControls, + StepperNavigation, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/new-york/ui/stepper" + +const shippingSchema = z.object({ + address: z.string().min(1, "Address is required"), + city: z.string().min(1, "City is required"), + postalCode: z.string().min(5, "Postal code is required"), +}) + +const paymentSchema = z.object({ + cardNumber: z.string().min(16, "Card number is required"), + expirationDate: z.string().min(5, "Expiration date is required"), + cvv: z.string().min(3, "CVV is required"), +}) + +type ShippingFormValues = z.infer +type PaymentFormValues = z.infer + +const stepperInstance = defineStepper( + { + id: "shipping", + title: "Shipping", + schema: shippingSchema, + }, + { + id: "payment", + title: "Payment", + schema: paymentSchema, + }, + { + id: "complete", + title: "Complete", + schema: z.object({}), + } +) + +export default function StepperForm() { + return ( + + + + ) +} + +const FormStepperComponent = () => { + const { steps, useStepper, utils } = stepperInstance + const methods = useStepper() + + const form = useForm({ + mode: "onTouched", + resolver: zodResolver(methods.current.schema), + }) + + const onSubmit = (values: z.infer) => { + console.log(`Form values for step ${methods.current.id}:`, values) + } + + const currentIndex = utils.getIndex(methods.current.id) + + return ( +
    + + + {steps.map((step) => ( + { + const valid = await form.trigger() + if (!valid) return + if (utils.getIndex(step.id) - currentIndex > 1) return + methods.goTo(step.id) + }} + > + {step.title} + + ))} + + {methods.switch({ + shipping: () => , + payment: () => , + complete: () => , + })} + + Previous + { + const valid = await form.trigger() + if (!valid) return false + if (utils.getIndex(nextStep.id as any) - currentIndex > 1) + return false + return true + }} + > + Next + + Reset + + + + ) +} + +const ShippingForm = () => { + const { + register, + formState: { errors }, + } = useFormContext() + + return ( +
    +
    + + + {errors.address && ( + + {errors.address.message} + + )} +
    +
    + + + {errors.city && ( + + {errors.city.message} + + )} +
    +
    + + + {errors.postalCode && ( + + {errors.postalCode.message} + + )} +
    +
    + ) +} + +function PaymentForm() { + const { + register, + formState: { errors }, + } = useFormContext() + + return ( +
    +
    + + + {errors.cardNumber && ( + + {errors.cardNumber.message} + + )} +
    +
    + + + {errors.expirationDate && ( + + {errors.expirationDate.message} + + )} +
    +
    + + + {errors.cvv && ( + {errors.cvv.message} + )} +
    +
    + ) +} + +function CompleteComponent() { + return
    Thank you! Your order is complete.
    +} diff --git a/apps/www/registry/new-york/examples/stepper-forms.tsx b/apps/www/registry/new-york/examples/stepper-forms.tsx deleted file mode 100644 index 3c33b87dfd5..00000000000 --- a/apps/www/registry/new-york/examples/stepper-forms.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - Stepper, - StepperAction, - StepperControls, - StepperDescription, - StepperNavigation, - StepperPanel, - StepperStep, - StepperTitle, - defineStepper, -} from "@/registry/default/ui/stepper" - -const stepperInstance = defineStepper( - { - id: "step-1", - title: "Step 1", - }, - { - id: "step-2", - title: "Step 2", - }, - { - id: "step-3", - title: "Step 3", - } -) - -export default function StepperDemo() { - const steps = stepperInstance.steps - return ( - - - {({ methods }) => - steps.map((step) => ( - methods.goTo(step.id)} - > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - -
    - ) -} diff --git a/apps/www/registry/new-york/examples/stepper-icon.tsx b/apps/www/registry/new-york/examples/stepper-icon.tsx new file mode 100644 index 00000000000..0b68d60d57c --- /dev/null +++ b/apps/www/registry/new-york/examples/stepper-icon.tsx @@ -0,0 +1,74 @@ +import { HomeIcon, SettingsIcon, UserIcon } from "lucide-react" + +import { + Stepper, + StepperAction, + StepperControls, + StepperNavigation, + StepperPanel, + StepperStep, + StepperTitle, + defineStepper, +} from "@/registry/new-york/ui/stepper" + +const stepperInstance = defineStepper( + { + id: "step-1", + title: "Step 1", + icon: , + }, + { + id: "step-2", + title: "Step 2", + icon: , + }, + { + id: "step-3", + title: "Step 3", + icon: , + } +) + +export default function StepperIcon() { + const steps = stepperInstance.steps + return ( + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + icon={step.icon} + > + {step.title} + + ))} + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )} +
    + ) +} diff --git a/apps/www/registry/new-york/examples/stepper-label-orientation.tsx b/apps/www/registry/new-york/examples/stepper-label-orientation.tsx index a2992129231..78b5ec48602 100644 --- a/apps/www/registry/new-york/examples/stepper-label-orientation.tsx +++ b/apps/www/registry/new-york/examples/stepper-label-orientation.tsx @@ -1,71 +1,95 @@ +import * as React from "react" + +import { Label } from "@/registry/new-york/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" import { Stepper, StepperAction, StepperControls, - StepperDescription, StepperNavigation, StepperPanel, StepperStep, StepperTitle, defineStepper, -} from "@/registry/default/ui/stepper" +} from "@/registry/new-york/ui/stepper" + +type LabelOrientation = "horizontal" | "vertical" const stepperInstance = defineStepper( { id: "step-1", title: "Step 1", - description: "This is the first step", }, { id: "step-2", title: "Step 2", - description: "This is the second step", }, { id: "step-3", title: "Step 3", - description: "This is the third step", } ) -export default function StepperLabelOrientation() { +export default function StepperVariants() { const steps = stepperInstance.steps + + const [labelOrientation, setLabelOrientation] = + React.useState("horizontal") return ( - - - {({ methods }) => - steps.map((step) => ( - methods.goTo(step.id)} - > - {step.title} - - )) +
    + + setLabelOrientation(value as LabelOrientation) } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - - + > +
    + + +
    +
    + + +
    +
    + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + ))} + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )} +
    +
    ) } diff --git a/apps/www/registry/new-york/examples/stepper-responsive-variant.tsx b/apps/www/registry/new-york/examples/stepper-responsive-variant.tsx index 6f7ad1f31ad..3c429aea941 100644 --- a/apps/www/registry/new-york/examples/stepper-responsive-variant.tsx +++ b/apps/www/registry/new-york/examples/stepper-responsive-variant.tsx @@ -34,10 +34,10 @@ export default function StepperResponsiveVariant() { className="space-y-4" variant={isMobile ? "vertical" : "horizontal"} > - - {({ methods }) => - steps.map((step) => ( - <> + {({ methods }) => ( + <> + + {steps.map((step) => ( )} - - )) - } - - {!isMobile && - steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + ))} +
    + {!isMobile && + steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )}
    ) } diff --git a/apps/www/registry/new-york/examples/stepper-tracking.tsx b/apps/www/registry/new-york/examples/stepper-tracking.tsx index d0aedd1234f..1833929af50 100644 --- a/apps/www/registry/new-york/examples/stepper-tracking.tsx +++ b/apps/www/registry/new-york/examples/stepper-tracking.tsx @@ -1,14 +1,17 @@ +import * as React from "react" + +import { Label } from "@/registry/new-york/ui/label" +import { RadioGroup, RadioGroupItem } from "@/registry/new-york/ui/radio-group" import { Stepper, StepperAction, StepperControls, - StepperDescription, StepperNavigation, StepperPanel, StepperStep, StepperTitle, defineStepper, -} from "@/registry/default/ui/stepper" +} from "@/registry/new-york/ui/stepper" const stepperInstance = defineStepper( { @@ -22,47 +25,74 @@ const stepperInstance = defineStepper( { id: "step-3", title: "Step 3", + }, + { + id: "step-4", + title: "Step 4", + }, + { + id: "step-5", + title: "Step 5", + }, + { + id: "step-6", + title: "Step 6", } ) export default function StepperVerticalFollow() { const steps = stepperInstance.steps + + const [tracking, setTracking] = React.useState(false) return ( - - - {({ methods }) => - steps.map((step) => ( - methods.goTo(step.id)} - > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - -
    +
    + setTracking(value === "true")} + > +
    + + +
    +
    + + +
    +
    + + {({ methods }) => ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + +
    +

    + Content for {step.id} +

    +
    + + Previous + Next + Reset + +
    +
    + ))} +
    + + )} +
    +
    ) } diff --git a/apps/www/registry/new-york/examples/stepper-variants.tsx b/apps/www/registry/new-york/examples/stepper-variants.tsx index 36c88e6381d..38304e80969 100644 --- a/apps/www/registry/new-york/examples/stepper-variants.tsx +++ b/apps/www/registry/new-york/examples/stepper-variants.tsx @@ -38,21 +38,19 @@ export default function StepperVariants() {
    - setVariant(value as "horizontal" | "vertical" | "circle") - } + onValueChange={(value) => setVariant(value as Variant)} >
    - - + +
    - - + +
    - - + +
    {variant === "horizontal" && } @@ -70,35 +68,37 @@ const HorizontalStepper = () => { className="space-y-4" variant="horizontal" > - - {({ methods }) => - steps.map((step) => ( - ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + + ))} + + {steps.map((step) => ( + methods.goTo(step.id)} + when={step} + className="h-[200px] content-center rounded border bg-slate-50 p-8" > - {step.title} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ step }) => ( +

    Content for {step.id}

    + )} + + ))} + + Previous + Next + Reset + + + )} ) } @@ -111,10 +111,10 @@ const VerticalStepper = () => { className="space-y-4" variant="vertical" > - - {({ methods }) => - steps.map((step) => ( - <> + {({ methods }) => ( + <> + + {steps.map((step) => ( { )} - - )) - } - - - Previous - Next - Reset - + ))} + + + Previous + Next + Reset + + + )} ) } @@ -148,29 +148,31 @@ const CircleStepper = () => { const steps = stepperInstance.steps return ( - - {({ methods }) => ( - - {methods.current.title} - - )} - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ methods }) => ( + <> + + + {methods.current.title} + + + {steps.map((step) => ( + + {({ step }) => ( +

    Content for {step.id}

    + )} +
    + ))} + + Previous + Next + Reset + + + )}
    ) } diff --git a/apps/www/registry/new-york/examples/stepper-with-description.tsx b/apps/www/registry/new-york/examples/stepper-with-description.tsx index 40019860035..fd1709fe999 100644 --- a/apps/www/registry/new-york/examples/stepper-with-description.tsx +++ b/apps/www/registry/new-york/examples/stepper-with-description.tsx @@ -8,7 +8,7 @@ import { StepperStep, StepperTitle, defineStepper, -} from "@/registry/default/ui/stepper" +} from "@/registry/new-york/ui/stepper" const stepperInstance = defineStepper( { @@ -37,36 +37,38 @@ export default function StepperWithDescription() { variant="vertical" labelOrientation="horizontal" > - - {({ methods }) => - steps.map((step) => ( - ( + <> + + {steps.map((step) => ( + methods.goTo(step.id)} + > + {step.title} + {step.description} + + ))} + + {steps.map((step) => ( + methods.goTo(step.id)} + when={step} + className="h-[200px] content-center rounded border bg-slate-50 p-8" > - {step.title} - {step.description} - - )) - } - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - + {({ step }) => ( +

    Content for {step.id}

    + )} + + ))} + + Previous + Next + Reset + + + )} ) } diff --git a/apps/www/registry/new-york/ui/stepper.tsx b/apps/www/registry/new-york/ui/stepper.tsx index d1328e58db6..56e6aba3aa9 100644 --- a/apps/www/registry/new-york/ui/stepper.tsx +++ b/apps/www/registry/new-york/ui/stepper.tsx @@ -19,6 +19,7 @@ type StepperConfig = { instance: ReturnType> variant?: StepperVariant labelOrientation?: StepperLabelOrientation + tracking?: boolean } const StepContext = React.createContext>({ @@ -51,18 +52,27 @@ function Stepper({ variant = "horizontal", className, labelOrientation = "horizontal", + tracking = false, ...props -}: StepperConfig & React.ComponentProps<"div">) { +}: StepperConfig & + Omit, "children"> & { + children: + | React.ReactNode + | ((props: { methods: Stepperize.Stepper }) => React.ReactNode) + }) { const { instance } = props + const methods = instance.useStepper() as Stepperize.Stepper + return (
    - {children} + {typeof children === "function" ? children({ methods }) : children}
    ) @@ -74,11 +84,7 @@ const StepperNavigation = ({ "aria-label": ariaLabel = "Stepper Navigation", ...props }: Omit, "children"> & { - children: - | React.ReactNode - | ((props: { - methods: Stepperize.Stepper - }) => React.ReactNode) + children: React.ReactNode }) => { const { variant, instance } = useStepper() @@ -91,9 +97,7 @@ const StepperNavigation = ({ className={cn("stepper-navigation", className)} {...props} > -
      - {typeof children === "function" ? children({ methods }) : children} -
    +
      {children}
    ) } @@ -160,7 +164,7 @@ const StepperStep = ({
  • ({ orientation="horizontal" labelOrientation={labelOrientation} isLast={isLast} + state={dataState} + disabled={props.disabled} /> )}
    @@ -209,14 +215,24 @@ const StepperStep = ({
  • {variant === "horizontal" && labelOrientation === "horizontal" && ( - + )} {variant === "vertical" && (
    {!isLast && (
    - +
    )}
    {panel}
    @@ -230,13 +246,19 @@ const StepperSeparator = ({ orientation, isLast, labelOrientation, + state, + disabled, }: { isLast: boolean + state: string + disabled?: boolean } & VariantProps) => { if (isLast) return null return (
    @@ -245,7 +267,8 @@ const StepperSeparator = ({ const classForSeparator = cva( [ - "bg-muted group-data-[state=completed]:bg-primary group-data-[disabled]:opacity-50", + "bg-muted", + "data-[state=completed]:bg-primary data-[disabled]:opacity-50", "transition-all duration-300 ease-in-out", ], { @@ -427,7 +450,6 @@ const StepperPanel = ({ | React.ReactNode | ((props: { step: T - methods: Stepperize.Stepper onBeforeAction: ( action: StepAction, callback: (params: { @@ -438,7 +460,7 @@ const StepperPanel = ({ }) => React.ReactNode) }) => { const Comp = asChild ? Slot : "div" - const { instance } = useStepper() + const { instance, tracking } = useStepper() const methods = instance.useStepper() @@ -475,9 +497,13 @@ const StepperPanel = ({ return ( <> {methods.when(when.id, (step) => ( - + scrollIntoStepperPanel(node, tracking)} + {...props} + > {typeof children === "function" - ? children({ step: step as T, methods, onBeforeAction }) + ? children({ step: step as T, onBeforeAction }) : children} ))} @@ -485,6 +511,15 @@ const StepperPanel = ({ ) } +function scrollIntoStepperPanel( + node: HTMLDivElement | null, + tracking?: boolean +) { + if (tracking) { + node?.scrollIntoView({ behavior: "smooth", block: "center" }) + } +} + const StepperControls = ({ children, asChild, diff --git a/apps/www/registry/registry-examples.ts b/apps/www/registry/registry-examples.ts index 23efd773f57..60cc670c378 100644 --- a/apps/www/registry/registry-examples.ts +++ b/apps/www/registry/registry-examples.ts @@ -1127,6 +1127,61 @@ export const examples: Registry["items"] = [ }, ], }, + { + name: "stepper-description", + type: "registry:example", + registryDependencies: ["stepper"], + files: [ + { + path: "examples/stepper-description.tsx", + type: "registry:example", + }, + ], + }, + { + name: "stepper-label-orientation", + type: "registry:example", + registryDependencies: ["stepper"], + files: [ + { + path: "examples/stepper-label-orientation.tsx", + type: "registry:example", + }, + ], + }, + { + name: "stepper-tracking", + type: "registry:example", + registryDependencies: ["stepper"], + files: [ + { + path: "examples/stepper-tracking.tsx", + type: "registry:example", + }, + ], + }, + { + name: "stepper-icon", + type: "registry:example", + registryDependencies: ["stepper"], + files: [ + { + path: "examples/stepper-icon.tsx", + type: "registry:example", + }, + ], + }, + { + name: "stepper-form", + type: "registry:example", + registryDependencies: ["stepper", "form"], + files: [ + { + path: "examples/stepper-form.tsx", + type: "registry:example", + }, + ], + }, { name: "switch-demo", type: "registry:example", From 6166e778b8658a3053a1ee5bc721612ad389cf36 Mon Sep 17 00:00:00 2001 From: Damian Ricobelli Date: Mon, 27 Jan 2025 01:19:16 -0300 Subject: [PATCH 62/62] docs: update logic and docs --- apps/www/content/docs/components/stepper.mdx | 165 +++++----- apps/www/package.json | 2 +- apps/www/public/r/colors/index.json | 244 ++++++++++++++ .../public/r/styles/default/stepper-demo.json | 2 +- .../r/styles/default/stepper-description.json | 2 +- .../public/r/styles/default/stepper-form.json | 2 +- .../public/r/styles/default/stepper-icon.json | 2 +- .../default/stepper-label-orientation.json | 2 +- .../default/stepper-responsive-variant.json | 2 +- .../r/styles/default/stepper-tracking.json | 2 +- .../r/styles/default/stepper-variants.json | 2 +- apps/www/public/r/styles/default/stepper.json | 2 +- .../r/styles/new-york/stepper-demo.json | 2 +- .../styles/new-york/stepper-description.json | 2 +- .../r/styles/new-york/stepper-form.json | 2 +- .../r/styles/new-york/stepper-icon.json | 2 +- .../new-york/stepper-label-orientation.json | 2 +- .../new-york/stepper-responsive-variant.json | 2 +- .../r/styles/new-york/stepper-tracking.json | 2 +- .../r/styles/new-york/stepper-variants.json | 2 +- .../www/public/r/styles/new-york/stepper.json | 2 +- .../default/examples/stepper-demo.tsx | 50 +-- .../default/examples/stepper-description.tsx | 50 +-- .../default/examples/stepper-form.tsx | 187 +++++------ .../default/examples/stepper-icon.tsx | 51 +-- .../examples/stepper-label-orientation.tsx | 49 +-- .../examples/stepper-responsive-variant.tsx | 61 ++-- .../default/examples/stepper-tracking.tsx | 45 ++- .../default/examples/stepper-variants.tsx | 107 ++++--- .../examples/stepper-with-description.tsx | 74 ----- apps/www/registry/default/ui/stepper.tsx | 298 +++++------------- .../new-york/examples/stepper-demo.tsx | 50 +-- .../new-york/examples/stepper-description.tsx | 50 +-- .../new-york/examples/stepper-form.tsx | 187 +++++------ .../new-york/examples/stepper-icon.tsx | 51 +-- .../examples/stepper-label-orientation.tsx | 49 +-- .../examples/stepper-responsive-variant.tsx | 61 ++-- .../new-york/examples/stepper-tracking.tsx | 45 ++- .../new-york/examples/stepper-variants.tsx | 107 ++++--- .../examples/stepper-with-description.tsx | 74 ----- apps/www/registry/new-york/ui/stepper.tsx | 298 +++++------------- pnpm-lock.yaml | 37 +-- 42 files changed, 1217 insertions(+), 1211 deletions(-) delete mode 100644 apps/www/registry/default/examples/stepper-with-description.tsx delete mode 100644 apps/www/registry/new-york/examples/stepper-with-description.tsx diff --git a/apps/www/content/docs/components/stepper.mdx b/apps/www/content/docs/components/stepper.mdx index 4df49e06953..5ad08739655 100644 --- a/apps/www/content/docs/components/stepper.mdx +++ b/apps/www/content/docs/components/stepper.mdx @@ -61,15 +61,13 @@ A `Stepper` component is composed of the following parts: - `StepperTitle` - Step title. - `StepperDescription` - Step description. - `StepperPanel` - Section to render the step content based on the current step. -- `StepperControls` - Step controls to navigate through the steps. -- `StepperAction` - Next, previous and reset buttons. +- `StepperControls` - Section to render the buttons to navigate through the steps. ## Usage ```tsx showLineNumbers import { Stepper, - StepperAction, StepperControls, StepperDescription, StepperNavigation, @@ -96,10 +94,7 @@ export function Component() { ... - - - ... - + ... ) } @@ -138,17 +133,12 @@ export function MyFirstStepper() { ```tsx export function MyFirstStepper() { - const steps = stepperInstance.steps return ( {({ methods }) => ( - {steps.map((step) => ( - methods.goTo(step.id)} - > + {methods.all.map((step) => ( + methods.goTo(step.id)}> {step.title} ))} @@ -166,12 +156,16 @@ export function MyFirstStepper() { const steps = stepperInstance.steps return ( - {/* StepperNavigation code */} - {steps.map((step) => ( - - {({ step }) =>

    Content for {step.id}

    } -
    - ))} + {({ methods }) => ( + <> + {/* StepperNavigation code */} + {methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} + + )}
    ) } @@ -187,13 +181,26 @@ export function MyFirstStepper() { const steps = stepperInstance.steps return ( - {/* StepperNavigation code */} - {/* StepperPanel code */} - - Previous - Next - Reset - + {({ methods }) => ( + <> + {/* StepperNavigation code */} + {/* StepperPanel code */} + + {!methods.isLast && ( + + )} + + + + )} ) } @@ -203,43 +210,49 @@ export function MyFirstStepper() { ```tsx export function MyFirstStepper() { - const steps = stepperInstance.steps return ( {({ methods }) => ( <> - {steps.map((step) => ( - methods.goTo(step.id)} - > + {methods.all.map((step) => ( + methods.goTo(step.id)}> {step.title} ))} - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} + {methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} - Previous - Next - Reset + {!methods.isLast && ( + + )} + )}
    ) } + +const Content = ({ id }: { id: string }) => { + return ( + +

    Content for {id}

    +
    + ) +} ``` @@ -295,7 +308,7 @@ The children accept a function that has as parameters the `methods` prop. The `m If you don't need the `methods` prop, you can just pass the children directly - and get the methods from the `useStepper` hook. + and get the methods from the `useStepper` hook from your stepper instance. **Props** @@ -355,22 +368,14 @@ The `StepperDescription` component is used to render the description of the step ## StepperPanel -The `StepperPanel` component is used to render the content of the step. You just need to pass the `when` prop which is the step you want to render. - -The children accept a function that has as parameters the step and a function `onBeforeAction` that allows to execute a callback before an action `prev`, `next` or ` - - - You don't have to worry about the stepper knowing which panel to render. If - the step is not correct, the panel will not be rendered. - +The `StepperPanel` component is used to render the content of the step. **Props** -| Name | Type | Description | -| ---------- | ------------------------------------------------------- | ----------------------------------------- | -| `children` | `React.ReactNode or function({step, onBeforeCallback})` | Content to render. | -| `when` | `Step` | Used to conditionally render the content. | -| `asChild` | `boolean` | Render as child. | +| Name | Type | Description | +| ---------- | ----------------- | ------------------ | +| `children` | `React.ReactNode` | Content to render. | +| `asChild` | `boolean` | Render as child. | ## StepperControls @@ -383,23 +388,33 @@ The `StepperControls` component is used to render the buttons to navigate throug | `children` | `React.ReactNode` | Buttons to render. | | `asChild` | `boolean` | Render as child. | -### StepperAction +## Before/after actions -The `StepperAction` component is used to render the buttons to navigate through the steps. You just need to pass the `action` prop which is the action you want to perform. +You can add a callback to the `next` and `prev` methods to execute a callback before or after the action is executed. +**This is useful if you need to validate the form or check if the step is valid before moving to the prev/next step.** - - If you need to execute a function before the button action is executed, you - can use the `onBeforeAction` prop. - +For example: -**Props** +```tsx +methods.beforeNext(async () => { + const valid = await form.trigger() + if (!valid) return false + return true +}) +``` + +That function will validate the form and check if the step is valid before moving to the next step returning a boolean value. + +More info about the `beforeNext` and `beforePrev` methods can be found in the [API References](https://stepperize.vercel.app/docs/react/api-references/hook#beforeafter-functions). -| Name | Type | Description | -| ---------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `children` | `React.ReactNode` | Buttons to render. | -| `asChild` | `boolean` | Render as child. | -| `action` | `prev, next or reset` | Action to perform. | -| `onBeforeAction` | `(event: React.MouseEvent, prevStep: Step, nextStep: Step) => boolean` | Function to execute before the action is performed. | +## Skip steps + +Through the methods you can access functions like `goTo` to skip to a specific step. + +```tsx +// From step 1 to step 3 +methods.goTo("step-3") +``` ## Examples diff --git a/apps/www/package.json b/apps/www/package.json index 111de6e4b7f..3f1300eaab6 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -52,7 +52,7 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.6", - "@stepperize/react": "^4.1.3", + "@stepperize/react": "^4.2.0", "@tanstack/react-table": "^8.9.1", "@vercel/analytics": "^1.2.2", "@vercel/og": "^0.0.21", diff --git a/apps/www/public/r/colors/index.json b/apps/www/public/r/colors/index.json index c25158efb11..c06c452a3ff 100644 --- a/apps/www/public/r/colors/index.json +++ b/apps/www/public/r/colors/index.json @@ -6,6 +6,7 @@ "hex": "#000000", "rgb": "rgb(0,0,0)", "hsl": "hsl(0,0%,0%)", + "oklch": "oklch(0.00,0.00,0)", "rgbChannel": "0 0 0", "hslChannel": "0 0% 0%" }, @@ -13,6 +14,7 @@ "hex": "#ffffff", "rgb": "rgb(255,255,255)", "hsl": "hsl(0,0%,100%)", + "oklch": "oklch(1.00,0.00,0)", "rgbChannel": "255 255 255", "hslChannel": "0 0% 100%" }, @@ -22,6 +24,7 @@ "hex": "#f8fafc", "rgb": "rgb(248,250,252)", "hsl": "hsl(210,40%,98%)", + "oklch": "oklch(0.98,0.00,248)", "rgbChannel": "248 250 252", "hslChannel": "210 40% 98%" }, @@ -30,6 +33,7 @@ "hex": "#f1f5f9", "rgb": "rgb(241,245,249)", "hsl": "hsl(210,40%,96.1%)", + "oklch": "oklch(0.97,0.01,248)", "rgbChannel": "241 245 249", "hslChannel": "210 40% 96.1%" }, @@ -38,6 +42,7 @@ "hex": "#e2e8f0", "rgb": "rgb(226,232,240)", "hsl": "hsl(214.3,31.8%,91.4%)", + "oklch": "oklch(0.93,0.01,256)", "rgbChannel": "226 232 240", "hslChannel": "214.3 31.8% 91.4%" }, @@ -46,6 +51,7 @@ "hex": "#cbd5e1", "rgb": "rgb(203,213,225)", "hsl": "hsl(212.7,26.8%,83.9%)", + "oklch": "oklch(0.87,0.02,253)", "rgbChannel": "203 213 225", "hslChannel": "212.7 26.8% 83.9%" }, @@ -54,6 +60,7 @@ "hex": "#94a3b8", "rgb": "rgb(148,163,184)", "hsl": "hsl(215,20.2%,65.1%)", + "oklch": "oklch(0.71,0.04,257)", "rgbChannel": "148 163 184", "hslChannel": "215 20.2% 65.1%" }, @@ -62,6 +69,7 @@ "hex": "#64748b", "rgb": "rgb(100,116,139)", "hsl": "hsl(215.4,16.3%,46.9%)", + "oklch": "oklch(0.55,0.04,257)", "rgbChannel": "100 116 139", "hslChannel": "215.4 16.3% 46.9%" }, @@ -70,6 +78,7 @@ "hex": "#475569", "rgb": "rgb(71,85,105)", "hsl": "hsl(215.3,19.3%,34.5%)", + "oklch": "oklch(0.45,0.04,257)", "rgbChannel": "71 85 105", "hslChannel": "215.3 19.3% 34.5%" }, @@ -78,6 +87,7 @@ "hex": "#334155", "rgb": "rgb(51,65,85)", "hsl": "hsl(215.3,25%,26.7%)", + "oklch": "oklch(0.37,0.04,257)", "rgbChannel": "51 65 85", "hslChannel": "215.3 25% 26.7%" }, @@ -86,6 +96,7 @@ "hex": "#1e293b", "rgb": "rgb(30,41,59)", "hsl": "hsl(217.2,32.6%,17.5%)", + "oklch": "oklch(0.28,0.04,260)", "rgbChannel": "30 41 59", "hslChannel": "217.2 32.6% 17.5%" }, @@ -94,6 +105,7 @@ "hex": "#0f172a", "rgb": "rgb(15,23,42)", "hsl": "hsl(222.2,47.4%,11.2%)", + "oklch": "oklch(0.21,0.04,266)", "rgbChannel": "15 23 42", "hslChannel": "222.2 47.4% 11.2%" }, @@ -102,6 +114,7 @@ "hex": "#020617", "rgb": "rgb(2,6,23)", "hsl": "hsl(222.2,84%,4.9%)", + "oklch": "oklch(0.13,0.04,265)", "rgbChannel": "2 6 23", "hslChannel": "222.2 84% 4.9%" } @@ -112,6 +125,7 @@ "hex": "#f9fafb", "rgb": "rgb(249,250,251)", "hsl": "hsl(210,20%,98%)", + "oklch": "oklch(0.98,0.00,248)", "rgbChannel": "249 250 251", "hslChannel": "210 20% 98%" }, @@ -120,6 +134,7 @@ "hex": "#f3f4f6", "rgb": "rgb(243,244,246)", "hsl": "hsl(220,14.3%,95.9%)", + "oklch": "oklch(0.97,0.00,265)", "rgbChannel": "243 244 246", "hslChannel": "220 14.3% 95.9%" }, @@ -128,6 +143,7 @@ "hex": "#e5e7eb", "rgb": "rgb(229,231,235)", "hsl": "hsl(220,13%,91%)", + "oklch": "oklch(0.93,0.01,265)", "rgbChannel": "229 231 235", "hslChannel": "220 13% 91%" }, @@ -136,6 +152,7 @@ "hex": "#d1d5db", "rgb": "rgb(209,213,219)", "hsl": "hsl(216,12.2%,83.9%)", + "oklch": "oklch(0.87,0.01,258)", "rgbChannel": "209 213 219", "hslChannel": "216 12.2% 83.9%" }, @@ -144,6 +161,7 @@ "hex": "#9ca3af", "rgb": "rgb(156,163,175)", "hsl": "hsl(217.9,10.6%,64.9%)", + "oklch": "oklch(0.71,0.02,261)", "rgbChannel": "156 163 175", "hslChannel": "217.9 10.6% 64.9%" }, @@ -152,6 +170,7 @@ "hex": "#6b7280", "rgb": "rgb(107,114,128)", "hsl": "hsl(220,8.9%,46.1%)", + "oklch": "oklch(0.55,0.02,264)", "rgbChannel": "107 114 128", "hslChannel": "220 8.9% 46.1%" }, @@ -160,6 +179,7 @@ "hex": "#4b5563", "rgb": "rgb(75,85,99)", "hsl": "hsl(215,13.8%,34.1%)", + "oklch": "oklch(0.45,0.03,257)", "rgbChannel": "75 85 99", "hslChannel": "215 13.8% 34.1%" }, @@ -168,6 +188,7 @@ "hex": "#374151", "rgb": "rgb(55,65,81)", "hsl": "hsl(216.9,19.1%,26.7%)", + "oklch": "oklch(0.37,0.03,260)", "rgbChannel": "55 65 81", "hslChannel": "216.9 19.1% 26.7%" }, @@ -176,6 +197,7 @@ "hex": "#1f2937", "rgb": "rgb(31,41,55)", "hsl": "hsl(215,27.9%,16.9%)", + "oklch": "oklch(0.28,0.03,257)", "rgbChannel": "31 41 55", "hslChannel": "215 27.9% 16.9%" }, @@ -184,6 +206,7 @@ "hex": "#111827", "rgb": "rgb(17,24,39)", "hsl": "hsl(220.9,39.3%,11%)", + "oklch": "oklch(0.21,0.03,265)", "rgbChannel": "17 24 39", "hslChannel": "220.9 39.3% 11%" }, @@ -192,6 +215,7 @@ "hex": "#030712", "rgb": "rgb(3,7,18)", "hsl": "hsl(224,71.4%,4.1%)", + "oklch": "oklch(0.13,0.03,262)", "rgbChannel": "3 7 18", "hslChannel": "224 71.4% 4.1%" } @@ -202,6 +226,7 @@ "hex": "#fafafa", "rgb": "rgb(250,250,250)", "hsl": "hsl(0,0%,98%)", + "oklch": "oklch(0.99,0.00,0)", "rgbChannel": "250 250 250", "hslChannel": "0 0% 98%" }, @@ -210,6 +235,7 @@ "hex": "#f4f4f5", "rgb": "rgb(244,244,245)", "hsl": "hsl(240,4.8%,95.9%)", + "oklch": "oklch(0.97,0.00,286)", "rgbChannel": "244 244 245", "hslChannel": "240 4.8% 95.9%" }, @@ -218,6 +244,7 @@ "hex": "#e4e4e7", "rgb": "rgb(228,228,231)", "hsl": "hsl(240,5.9%,90%)", + "oklch": "oklch(0.92,0.00,286)", "rgbChannel": "228 228 231", "hslChannel": "240 5.9% 90%" }, @@ -226,6 +253,7 @@ "hex": "#d4d4d8", "rgb": "rgb(212,212,216)", "hsl": "hsl(240,4.9%,83.9%)", + "oklch": "oklch(0.87,0.01,286)", "rgbChannel": "212 212 216", "hslChannel": "240 4.9% 83.9%" }, @@ -234,6 +262,7 @@ "hex": "#a1a1aa", "rgb": "rgb(161,161,170)", "hsl": "hsl(240,5%,64.9%)", + "oklch": "oklch(0.71,0.01,286)", "rgbChannel": "161 161 170", "hslChannel": "240 5% 64.9%" }, @@ -242,6 +271,7 @@ "hex": "#71717a", "rgb": "rgb(113,113,122)", "hsl": "hsl(240,3.8%,46.1%)", + "oklch": "oklch(0.55,0.01,286)", "rgbChannel": "113 113 122", "hslChannel": "240 3.8% 46.1%" }, @@ -250,6 +280,7 @@ "hex": "#52525b", "rgb": "rgb(82,82,91)", "hsl": "hsl(240,5.2%,33.9%)", + "oklch": "oklch(0.44,0.01,286)", "rgbChannel": "82 82 91", "hslChannel": "240 5.2% 33.9%" }, @@ -258,6 +289,7 @@ "hex": "#3f3f46", "rgb": "rgb(63,63,70)", "hsl": "hsl(240,5.3%,26.1%)", + "oklch": "oklch(0.37,0.01,286)", "rgbChannel": "63 63 70", "hslChannel": "240 5.3% 26.1%" }, @@ -266,6 +298,7 @@ "hex": "#27272a", "rgb": "rgb(39,39,42)", "hsl": "hsl(240,3.7%,15.9%)", + "oklch": "oklch(0.27,0.01,286)", "rgbChannel": "39 39 42", "hslChannel": "240 3.7% 15.9%" }, @@ -274,6 +307,7 @@ "hex": "#18181b", "rgb": "rgb(24,24,27)", "hsl": "hsl(240,5.9%,10%)", + "oklch": "oklch(0.21,0.01,286)", "rgbChannel": "24 24 27", "hslChannel": "240 5.9% 10%" }, @@ -282,6 +316,7 @@ "hex": "#09090b", "rgb": "rgb(9,9,11)", "hsl": "hsl(240,10%,3.9%)", + "oklch": "oklch(0.14,0.00,286)", "rgbChannel": "9 9 11", "hslChannel": "240 10% 3.9%" } @@ -292,6 +327,7 @@ "hex": "#fafafa", "rgb": "rgb(250,250,250)", "hsl": "hsl(0,0%,98%)", + "oklch": "oklch(0.99,0.00,0)", "rgbChannel": "250 250 250", "hslChannel": "0 0% 98%" }, @@ -300,6 +336,7 @@ "hex": "#f5f5f5", "rgb": "rgb(245,245,245)", "hsl": "hsl(0,0%,96.1%)", + "oklch": "oklch(0.97,0.00,0)", "rgbChannel": "245 245 245", "hslChannel": "0 0% 96.1%" }, @@ -308,6 +345,7 @@ "hex": "#e5e5e5", "rgb": "rgb(229,229,229)", "hsl": "hsl(0,0%,89.8%)", + "oklch": "oklch(0.92,0.00,0)", "rgbChannel": "229 229 229", "hslChannel": "0 0% 89.8%" }, @@ -316,6 +354,7 @@ "hex": "#d4d4d4", "rgb": "rgb(212,212,212)", "hsl": "hsl(0,0%,83.1%)", + "oklch": "oklch(0.87,0.00,0)", "rgbChannel": "212 212 212", "hslChannel": "0 0% 83.1%" }, @@ -324,6 +363,7 @@ "hex": "#a3a3a3", "rgb": "rgb(163,163,163)", "hsl": "hsl(0,0%,63.9%)", + "oklch": "oklch(0.72,0.00,0)", "rgbChannel": "163 163 163", "hslChannel": "0 0% 63.9%" }, @@ -332,6 +372,7 @@ "hex": "#737373", "rgb": "rgb(115,115,115)", "hsl": "hsl(0,0%,45.1%)", + "oklch": "oklch(0.56,0.00,0)", "rgbChannel": "115 115 115", "hslChannel": "0 0% 45.1%" }, @@ -340,6 +381,7 @@ "hex": "#525252", "rgb": "rgb(82,82,82)", "hsl": "hsl(0,0%,32.2%)", + "oklch": "oklch(0.44,0.00,0)", "rgbChannel": "82 82 82", "hslChannel": "0 0% 32.2%" }, @@ -348,6 +390,7 @@ "hex": "#404040", "rgb": "rgb(64,64,64)", "hsl": "hsl(0,0%,25.1%)", + "oklch": "oklch(0.37,0.00,0)", "rgbChannel": "64 64 64", "hslChannel": "0 0% 25.1%" }, @@ -356,6 +399,7 @@ "hex": "#262626", "rgb": "rgb(38,38,38)", "hsl": "hsl(0,0%,14.9%)", + "oklch": "oklch(0.27,0.00,0)", "rgbChannel": "38 38 38", "hslChannel": "0 0% 14.9%" }, @@ -364,6 +408,7 @@ "hex": "#171717", "rgb": "rgb(23,23,23)", "hsl": "hsl(0,0%,9%)", + "oklch": "oklch(0.20,0.00,0)", "rgbChannel": "23 23 23", "hslChannel": "0 0% 9%" }, @@ -372,6 +417,7 @@ "hex": "#0a0a0a", "rgb": "rgb(10,10,10)", "hsl": "hsl(0,0%,3.9%)", + "oklch": "oklch(0.14,0.00,0)", "rgbChannel": "10 10 10", "hslChannel": "0 0% 3.9%" } @@ -382,6 +428,7 @@ "hex": "#fafaf9", "rgb": "rgb(250,250,249)", "hsl": "hsl(60,9.1%,97.8%)", + "oklch": "oklch(0.98,0.00,106)", "rgbChannel": "250 250 249", "hslChannel": "60 9.1% 97.8%" }, @@ -390,6 +437,7 @@ "hex": "#f5f5f4", "rgb": "rgb(245,245,244)", "hsl": "hsl(60,4.8%,95.9%)", + "oklch": "oklch(0.97,0.00,106)", "rgbChannel": "245 245 244", "hslChannel": "60 4.8% 95.9%" }, @@ -398,6 +446,7 @@ "hex": "#e7e5e4", "rgb": "rgb(231,229,228)", "hsl": "hsl(20,5.9%,90%)", + "oklch": "oklch(0.92,0.00,49)", "rgbChannel": "231 229 228", "hslChannel": "20 5.9% 90%" }, @@ -406,6 +455,7 @@ "hex": "#d6d3d1", "rgb": "rgb(214,211,209)", "hsl": "hsl(24,5.7%,82.9%)", + "oklch": "oklch(0.87,0.00,56)", "rgbChannel": "214 211 209", "hslChannel": "24 5.7% 82.9%" }, @@ -414,6 +464,7 @@ "hex": "#a8a29e", "rgb": "rgb(168,162,158)", "hsl": "hsl(24,5.4%,63.9%)", + "oklch": "oklch(0.72,0.01,56)", "rgbChannel": "168 162 158", "hslChannel": "24 5.4% 63.9%" }, @@ -422,6 +473,7 @@ "hex": "#78716c", "rgb": "rgb(120,113,108)", "hsl": "hsl(25,5.3%,44.7%)", + "oklch": "oklch(0.55,0.01,58)", "rgbChannel": "120 113 108", "hslChannel": "25 5.3% 44.7%" }, @@ -430,6 +482,7 @@ "hex": "#57534e", "rgb": "rgb(87,83,78)", "hsl": "hsl(33.3,5.5%,32.4%)", + "oklch": "oklch(0.44,0.01,74)", "rgbChannel": "87 83 78", "hslChannel": "33.3 5.5% 32.4%" }, @@ -438,6 +491,7 @@ "hex": "#44403c", "rgb": "rgb(68,64,60)", "hsl": "hsl(30,6.3%,25.1%)", + "oklch": "oklch(0.37,0.01,68)", "rgbChannel": "68 64 60", "hslChannel": "30 6.3% 25.1%" }, @@ -446,6 +500,7 @@ "hex": "#292524", "rgb": "rgb(41,37,36)", "hsl": "hsl(12,6.5%,15.1%)", + "oklch": "oklch(0.27,0.01,34)", "rgbChannel": "41 37 36", "hslChannel": "12 6.5% 15.1%" }, @@ -454,6 +509,7 @@ "hex": "#1c1917", "rgb": "rgb(28,25,23)", "hsl": "hsl(24,9.8%,10%)", + "oklch": "oklch(0.22,0.01,56)", "rgbChannel": "28 25 23", "hslChannel": "24 9.8% 10%" }, @@ -462,6 +518,7 @@ "hex": "#0c0a09", "rgb": "rgb(12,10,9)", "hsl": "hsl(20,14.3%,4.1%)", + "oklch": "oklch(0.15,0.00,49)", "rgbChannel": "12 10 9", "hslChannel": "20 14.3% 4.1%" } @@ -472,6 +529,7 @@ "hex": "#fef2f2", "rgb": "rgb(254,242,242)", "hsl": "hsl(0,85.7%,97.3%)", + "oklch": "oklch(0.97,0.01,17)", "rgbChannel": "254 242 242", "hslChannel": "0 85.7% 97.3%" }, @@ -480,6 +538,7 @@ "hex": "#fee2e2", "rgb": "rgb(254,226,226)", "hsl": "hsl(0,93.3%,94.1%)", + "oklch": "oklch(0.94,0.03,18)", "rgbChannel": "254 226 226", "hslChannel": "0 93.3% 94.1%" }, @@ -488,6 +547,7 @@ "hex": "#fecaca", "rgb": "rgb(254,202,202)", "hsl": "hsl(0,96.3%,89.4%)", + "oklch": "oklch(0.88,0.06,18)", "rgbChannel": "254 202 202", "hslChannel": "0 96.3% 89.4%" }, @@ -496,6 +556,7 @@ "hex": "#fca5a5", "rgb": "rgb(252,165,165)", "hsl": "hsl(0,93.5%,81.8%)", + "oklch": "oklch(0.81,0.10,20)", "rgbChannel": "252 165 165", "hslChannel": "0 93.5% 81.8%" }, @@ -504,6 +565,7 @@ "hex": "#f87171", "rgb": "rgb(248,113,113)", "hsl": "hsl(0,90.6%,70.8%)", + "oklch": "oklch(0.71,0.17,22)", "rgbChannel": "248 113 113", "hslChannel": "0 90.6% 70.8%" }, @@ -512,6 +574,7 @@ "hex": "#ef4444", "rgb": "rgb(239,68,68)", "hsl": "hsl(0,84.2%,60.2%)", + "oklch": "oklch(0.64,0.21,25)", "rgbChannel": "239 68 68", "hslChannel": "0 84.2% 60.2%" }, @@ -520,6 +583,7 @@ "hex": "#dc2626", "rgb": "rgb(220,38,38)", "hsl": "hsl(0,72.2%,50.6%)", + "oklch": "oklch(0.58,0.22,27)", "rgbChannel": "220 38 38", "hslChannel": "0 72.2% 50.6%" }, @@ -528,6 +592,7 @@ "hex": "#b91c1c", "rgb": "rgb(185,28,28)", "hsl": "hsl(0,73.7%,41.8%)", + "oklch": "oklch(0.51,0.19,28)", "rgbChannel": "185 28 28", "hslChannel": "0 73.7% 41.8%" }, @@ -536,6 +601,7 @@ "hex": "#991b1b", "rgb": "rgb(153,27,27)", "hsl": "hsl(0,70%,35.3%)", + "oklch": "oklch(0.44,0.16,27)", "rgbChannel": "153 27 27", "hslChannel": "0 70% 35.3%" }, @@ -544,6 +610,7 @@ "hex": "#7f1d1d", "rgb": "rgb(127,29,29)", "hsl": "hsl(0,62.8%,30.6%)", + "oklch": "oklch(0.40,0.13,26)", "rgbChannel": "127 29 29", "hslChannel": "0 62.8% 30.6%" }, @@ -552,6 +619,7 @@ "hex": "#450a0a", "rgb": "rgb(69,10,10)", "hsl": "hsl(0,74.7%,15.5%)", + "oklch": "oklch(0.26,0.09,26)", "rgbChannel": "69 10 10", "hslChannel": "0 74.7% 15.5%" } @@ -562,6 +630,7 @@ "hex": "#fff7ed", "rgb": "rgb(255,247,237)", "hsl": "hsl(33.3,100%,96.5%)", + "oklch": "oklch(0.98,0.02,74)", "rgbChannel": "255 247 237", "hslChannel": "33.3 100% 96.5%" }, @@ -570,6 +639,7 @@ "hex": "#ffedd5", "rgb": "rgb(255,237,213)", "hsl": "hsl(34.3,100%,91.8%)", + "oklch": "oklch(0.95,0.04,75)", "rgbChannel": "255 237 213", "hslChannel": "34.3 100% 91.8%" }, @@ -578,6 +648,7 @@ "hex": "#fed7aa", "rgb": "rgb(254,215,170)", "hsl": "hsl(32.1,97.7%,83.1%)", + "oklch": "oklch(0.90,0.07,71)", "rgbChannel": "254 215 170", "hslChannel": "32.1 97.7% 83.1%" }, @@ -586,6 +657,7 @@ "hex": "#fdba74", "rgb": "rgb(253,186,116)", "hsl": "hsl(30.7,97.2%,72.4%)", + "oklch": "oklch(0.84,0.12,66)", "rgbChannel": "253 186 116", "hslChannel": "30.7 97.2% 72.4%" }, @@ -594,6 +666,7 @@ "hex": "#fb923c", "rgb": "rgb(251,146,60)", "hsl": "hsl(27,96%,61%)", + "oklch": "oklch(0.76,0.16,56)", "rgbChannel": "251 146 60", "hslChannel": "27 96% 61%" }, @@ -602,6 +675,7 @@ "hex": "#f97316", "rgb": "rgb(249,115,22)", "hsl": "hsl(24.6,95%,53.1%)", + "oklch": "oklch(0.70,0.19,48)", "rgbChannel": "249 115 22", "hslChannel": "24.6 95% 53.1%" }, @@ -610,6 +684,7 @@ "hex": "#ea580c", "rgb": "rgb(234,88,12)", "hsl": "hsl(20.5,90.2%,48.2%)", + "oklch": "oklch(0.65,0.19,41)", "rgbChannel": "234 88 12", "hslChannel": "20.5 90.2% 48.2%" }, @@ -618,6 +693,7 @@ "hex": "#c2410c", "rgb": "rgb(194,65,12)", "hsl": "hsl(17.5,88.3%,40.4%)", + "oklch": "oklch(0.55,0.17,38)", "rgbChannel": "194 65 12", "hslChannel": "17.5 88.3% 40.4%" }, @@ -626,6 +702,7 @@ "hex": "#9a3412", "rgb": "rgb(154,52,18)", "hsl": "hsl(15,79.1%,33.7%)", + "oklch": "oklch(0.47,0.14,37)", "rgbChannel": "154 52 18", "hslChannel": "15 79.1% 33.7%" }, @@ -634,6 +711,7 @@ "hex": "#7c2d12", "rgb": "rgb(124,45,18)", "hsl": "hsl(15.3,74.6%,27.8%)", + "oklch": "oklch(0.41,0.12,38)", "rgbChannel": "124 45 18", "hslChannel": "15.3 74.6% 27.8%" }, @@ -642,6 +720,7 @@ "hex": "#431407", "rgb": "rgb(67,20,7)", "hsl": "hsl(13,81.1%,14.5%)", + "oklch": "oklch(0.27,0.08,36)", "rgbChannel": "67 20 7", "hslChannel": "13 81.1% 14.5%" } @@ -652,6 +731,7 @@ "hex": "#fffbeb", "rgb": "rgb(255,251,235)", "hsl": "hsl(48,100%,96.1%)", + "oklch": "oklch(0.99,0.02,95)", "rgbChannel": "255 251 235", "hslChannel": "48 100% 96.1%" }, @@ -660,6 +740,7 @@ "hex": "#fef3c7", "rgb": "rgb(254,243,199)", "hsl": "hsl(48,96.5%,88.8%)", + "oklch": "oklch(0.96,0.06,96)", "rgbChannel": "254 243 199", "hslChannel": "48 96.5% 88.8%" }, @@ -668,6 +749,7 @@ "hex": "#fde68a", "rgb": "rgb(253,230,138)", "hsl": "hsl(48,96.6%,76.7%)", + "oklch": "oklch(0.92,0.12,96)", "rgbChannel": "253 230 138", "hslChannel": "48 96.6% 76.7%" }, @@ -676,6 +758,7 @@ "hex": "#fcd34d", "rgb": "rgb(252,211,77)", "hsl": "hsl(45.9,96.7%,64.5%)", + "oklch": "oklch(0.88,0.15,92)", "rgbChannel": "252 211 77", "hslChannel": "45.9 96.7% 64.5%" }, @@ -684,6 +767,7 @@ "hex": "#fbbf24", "rgb": "rgb(251,191,36)", "hsl": "hsl(43.3,96.4%,56.3%)", + "oklch": "oklch(0.84,0.16,84)", "rgbChannel": "251 191 36", "hslChannel": "43.3 96.4% 56.3%" }, @@ -692,6 +776,7 @@ "hex": "#f59e0b", "rgb": "rgb(245,158,11)", "hsl": "hsl(37.7,92.1%,50.2%)", + "oklch": "oklch(0.77,0.16,70)", "rgbChannel": "245 158 11", "hslChannel": "37.7 92.1% 50.2%" }, @@ -700,6 +785,7 @@ "hex": "#d97706", "rgb": "rgb(217,119,6)", "hsl": "hsl(32.1,94.6%,43.7%)", + "oklch": "oklch(0.67,0.16,58)", "rgbChannel": "217 119 6", "hslChannel": "32.1 94.6% 43.7%" }, @@ -708,6 +794,7 @@ "hex": "#b45309", "rgb": "rgb(180,83,9)", "hsl": "hsl(26,90.5%,37.1%)", + "oklch": "oklch(0.56,0.15,49)", "rgbChannel": "180 83 9", "hslChannel": "26 90.5% 37.1%" }, @@ -716,6 +803,7 @@ "hex": "#92400e", "rgb": "rgb(146,64,14)", "hsl": "hsl(22.7,82.5%,31.4%)", + "oklch": "oklch(0.47,0.12,46)", "rgbChannel": "146 64 14", "hslChannel": "22.7 82.5% 31.4%" }, @@ -724,6 +812,7 @@ "hex": "#78350f", "rgb": "rgb(120,53,15)", "hsl": "hsl(21.7,77.8%,26.5%)", + "oklch": "oklch(0.41,0.11,46)", "rgbChannel": "120 53 15", "hslChannel": "21.7 77.8% 26.5%" }, @@ -732,6 +821,7 @@ "hex": "#451a03", "rgb": "rgb(69,26,3)", "hsl": "hsl(20.9,91.7%,14.1%)", + "oklch": "oklch(0.28,0.07,46)", "rgbChannel": "69 26 3", "hslChannel": "20.9 91.7% 14.1%" } @@ -742,6 +832,7 @@ "hex": "#fefce8", "rgb": "rgb(254,252,232)", "hsl": "hsl(54.5,91.7%,95.3%)", + "oklch": "oklch(0.99,0.03,102)", "rgbChannel": "254 252 232", "hslChannel": "54.5 91.7% 95.3%" }, @@ -750,6 +841,7 @@ "hex": "#fef9c3", "rgb": "rgb(254,249,195)", "hsl": "hsl(54.9,96.7%,88%)", + "oklch": "oklch(0.97,0.07,103)", "rgbChannel": "254 249 195", "hslChannel": "54.9 96.7% 88%" }, @@ -758,6 +850,7 @@ "hex": "#fef08a", "rgb": "rgb(254,240,138)", "hsl": "hsl(52.8,98.3%,76.9%)", + "oklch": "oklch(0.95,0.12,102)", "rgbChannel": "254 240 138", "hslChannel": "52.8 98.3% 76.9%" }, @@ -766,6 +859,7 @@ "hex": "#fde047", "rgb": "rgb(253,224,71)", "hsl": "hsl(50.4,97.8%,63.5%)", + "oklch": "oklch(0.91,0.17,98)", "rgbChannel": "253 224 71", "hslChannel": "50.4 97.8% 63.5%" }, @@ -774,6 +868,7 @@ "hex": "#facc15", "rgb": "rgb(250,204,21)", "hsl": "hsl(47.9,95.8%,53.1%)", + "oklch": "oklch(0.86,0.17,92)", "rgbChannel": "250 204 21", "hslChannel": "47.9 95.8% 53.1%" }, @@ -782,6 +877,7 @@ "hex": "#eab308", "rgb": "rgb(234,179,8)", "hsl": "hsl(45.4,93.4%,47.5%)", + "oklch": "oklch(0.80,0.16,86)", "rgbChannel": "234 179 8", "hslChannel": "45.4 93.4% 47.5%" }, @@ -790,6 +886,7 @@ "hex": "#ca8a04", "rgb": "rgb(202,138,4)", "hsl": "hsl(40.6,96.1%,40.4%)", + "oklch": "oklch(0.68,0.14,76)", "rgbChannel": "202 138 4", "hslChannel": "40.6 96.1% 40.4%" }, @@ -798,6 +895,7 @@ "hex": "#a16207", "rgb": "rgb(161,98,7)", "hsl": "hsl(35.5,91.7%,32.9%)", + "oklch": "oklch(0.55,0.12,66)", "rgbChannel": "161 98 7", "hslChannel": "35.5 91.7% 32.9%" }, @@ -806,6 +904,7 @@ "hex": "#854d0e", "rgb": "rgb(133,77,14)", "hsl": "hsl(31.8,81%,28.8%)", + "oklch": "oklch(0.48,0.10,62)", "rgbChannel": "133 77 14", "hslChannel": "31.8 81% 28.8%" }, @@ -814,6 +913,7 @@ "hex": "#713f12", "rgb": "rgb(113,63,18)", "hsl": "hsl(28.4,72.5%,25.7%)", + "oklch": "oklch(0.42,0.09,58)", "rgbChannel": "113 63 18", "hslChannel": "28.4 72.5% 25.7%" }, @@ -822,6 +922,7 @@ "hex": "#422006", "rgb": "rgb(66,32,6)", "hsl": "hsl(26,83.3%,14.1%)", + "oklch": "oklch(0.29,0.06,54)", "rgbChannel": "66 32 6", "hslChannel": "26 83.3% 14.1%" } @@ -832,6 +933,7 @@ "hex": "#f7fee7", "rgb": "rgb(247,254,231)", "hsl": "hsl(78.3,92%,95.1%)", + "oklch": "oklch(0.99,0.03,121)", "rgbChannel": "247 254 231", "hslChannel": "78.3 92% 95.1%" }, @@ -840,6 +942,7 @@ "hex": "#ecfccb", "rgb": "rgb(236,252,203)", "hsl": "hsl(79.6,89.1%,89.2%)", + "oklch": "oklch(0.97,0.07,122)", "rgbChannel": "236 252 203", "hslChannel": "79.6 89.1% 89.2%" }, @@ -848,6 +951,7 @@ "hex": "#d9f99d", "rgb": "rgb(217,249,157)", "hsl": "hsl(80.9,88.5%,79.6%)", + "oklch": "oklch(0.94,0.12,124)", "rgbChannel": "217 249 157", "hslChannel": "80.9 88.5% 79.6%" }, @@ -856,6 +960,7 @@ "hex": "#bef264", "rgb": "rgb(190,242,100)", "hsl": "hsl(82,84.5%,67.1%)", + "oklch": "oklch(0.90,0.18,127)", "rgbChannel": "190 242 100", "hslChannel": "82 84.5% 67.1%" }, @@ -864,6 +969,7 @@ "hex": "#a3e635", "rgb": "rgb(163,230,53)", "hsl": "hsl(82.7,78%,55.5%)", + "oklch": "oklch(0.85,0.21,129)", "rgbChannel": "163 230 53", "hslChannel": "82.7 78% 55.5%" }, @@ -872,6 +978,7 @@ "hex": "#84cc16", "rgb": "rgb(132,204,22)", "hsl": "hsl(83.7,80.5%,44.3%)", + "oklch": "oklch(0.77,0.20,131)", "rgbChannel": "132 204 22", "hslChannel": "83.7 80.5% 44.3%" }, @@ -880,6 +987,7 @@ "hex": "#65a30d", "rgb": "rgb(101,163,13)", "hsl": "hsl(84.8,85.2%,34.5%)", + "oklch": "oklch(0.65,0.18,132)", "rgbChannel": "101 163 13", "hslChannel": "84.8 85.2% 34.5%" }, @@ -888,6 +996,7 @@ "hex": "#4d7c0f", "rgb": "rgb(77,124,15)", "hsl": "hsl(85.9,78.4%,27.3%)", + "oklch": "oklch(0.53,0.14,132)", "rgbChannel": "77 124 15", "hslChannel": "85.9 78.4% 27.3%" }, @@ -896,6 +1005,7 @@ "hex": "#3f6212", "rgb": "rgb(63,98,18)", "hsl": "hsl(86.3,69%,22.7%)", + "oklch": "oklch(0.45,0.11,131)", "rgbChannel": "63 98 18", "hslChannel": "86.3 69% 22.7%" }, @@ -904,6 +1014,7 @@ "hex": "#365314", "rgb": "rgb(54,83,20)", "hsl": "hsl(87.6,61.2%,20.2%)", + "oklch": "oklch(0.41,0.10,131)", "rgbChannel": "54 83 20", "hslChannel": "87.6 61.2% 20.2%" }, @@ -912,6 +1023,7 @@ "hex": "#1a2e05", "rgb": "rgb(26,46,5)", "hsl": "hsl(89.3,80.4%,10%)", + "oklch": "oklch(0.27,0.07,132)", "rgbChannel": "26 46 5", "hslChannel": "89.3 80.4% 10%" } @@ -922,6 +1034,7 @@ "hex": "#f0fdf4", "rgb": "rgb(240,253,244)", "hsl": "hsl(138.5,76.5%,96.7%)", + "oklch": "oklch(0.98,0.02,156)", "rgbChannel": "240 253 244", "hslChannel": "138.5 76.5% 96.7%" }, @@ -930,6 +1043,7 @@ "hex": "#dcfce7", "rgb": "rgb(220,252,231)", "hsl": "hsl(140.6,84.2%,92.5%)", + "oklch": "oklch(0.96,0.04,157)", "rgbChannel": "220 252 231", "hslChannel": "140.6 84.2% 92.5%" }, @@ -938,6 +1052,7 @@ "hex": "#bbf7d0", "rgb": "rgb(187,247,208)", "hsl": "hsl(141,78.9%,85.1%)", + "oklch": "oklch(0.93,0.08,156)", "rgbChannel": "187 247 208", "hslChannel": "141 78.9% 85.1%" }, @@ -946,6 +1061,7 @@ "hex": "#86efac", "rgb": "rgb(134,239,172)", "hsl": "hsl(141.7,76.6%,73.1%)", + "oklch": "oklch(0.87,0.14,154)", "rgbChannel": "134 239 172", "hslChannel": "141.7 76.6% 73.1%" }, @@ -954,6 +1070,7 @@ "hex": "#4ade80", "rgb": "rgb(74,222,128)", "hsl": "hsl(141.9,69.2%,58%)", + "oklch": "oklch(0.80,0.18,152)", "rgbChannel": "74 222 128", "hslChannel": "141.9 69.2% 58%" }, @@ -962,6 +1079,7 @@ "hex": "#22c55e", "rgb": "rgb(34,197,94)", "hsl": "hsl(142.1,70.6%,45.3%)", + "oklch": "oklch(0.72,0.19,150)", "rgbChannel": "34 197 94", "hslChannel": "142.1 70.6% 45.3%" }, @@ -970,6 +1088,7 @@ "hex": "#16a34a", "rgb": "rgb(22,163,74)", "hsl": "hsl(142.1,76.2%,36.3%)", + "oklch": "oklch(0.63,0.17,149)", "rgbChannel": "22 163 74", "hslChannel": "142.1 76.2% 36.3%" }, @@ -978,6 +1097,7 @@ "hex": "#15803d", "rgb": "rgb(21,128,61)", "hsl": "hsl(142.4,71.8%,29.2%)", + "oklch": "oklch(0.53,0.14,150)", "rgbChannel": "21 128 61", "hslChannel": "142.4 71.8% 29.2%" }, @@ -986,6 +1106,7 @@ "hex": "#166534", "rgb": "rgb(22,101,52)", "hsl": "hsl(142.8,64.2%,24.1%)", + "oklch": "oklch(0.45,0.11,151)", "rgbChannel": "22 101 52", "hslChannel": "142.8 64.2% 24.1%" }, @@ -994,6 +1115,7 @@ "hex": "#14532d", "rgb": "rgb(20,83,45)", "hsl": "hsl(143.8,61.2%,20.2%)", + "oklch": "oklch(0.39,0.09,153)", "rgbChannel": "20 83 45", "hslChannel": "143.8 61.2% 20.2%" }, @@ -1002,6 +1124,7 @@ "hex": "#052e16", "rgb": "rgb(5,46,22)", "hsl": "hsl(144.9,80.4%,10%)", + "oklch": "oklch(0.27,0.06,153)", "rgbChannel": "5 46 22", "hslChannel": "144.9 80.4% 10%" } @@ -1012,6 +1135,7 @@ "hex": "#ecfdf5", "rgb": "rgb(236,253,245)", "hsl": "hsl(151.8,81%,95.9%)", + "oklch": "oklch(0.98,0.02,166)", "rgbChannel": "236 253 245", "hslChannel": "151.8 81% 95.9%" }, @@ -1020,6 +1144,7 @@ "hex": "#d1fae5", "rgb": "rgb(209,250,229)", "hsl": "hsl(149.3,80.4%,90%)", + "oklch": "oklch(0.95,0.05,163)", "rgbChannel": "209 250 229", "hslChannel": "149.3 80.4% 90%" }, @@ -1028,6 +1153,7 @@ "hex": "#a7f3d0", "rgb": "rgb(167,243,208)", "hsl": "hsl(152.4,76%,80.4%)", + "oklch": "oklch(0.90,0.09,164)", "rgbChannel": "167 243 208", "hslChannel": "152.4 76% 80.4%" }, @@ -1036,6 +1162,7 @@ "hex": "#6ee7b7", "rgb": "rgb(110,231,183)", "hsl": "hsl(156.2,71.6%,66.9%)", + "oklch": "oklch(0.85,0.13,165)", "rgbChannel": "110 231 183", "hslChannel": "156.2 71.6% 66.9%" }, @@ -1044,6 +1171,7 @@ "hex": "#34d399", "rgb": "rgb(52,211,153)", "hsl": "hsl(158.1,64.4%,51.6%)", + "oklch": "oklch(0.77,0.15,163)", "rgbChannel": "52 211 153", "hslChannel": "158.1 64.4% 51.6%" }, @@ -1052,6 +1180,7 @@ "hex": "#10b981", "rgb": "rgb(16,185,129)", "hsl": "hsl(160.1,84.1%,39.4%)", + "oklch": "oklch(0.70,0.15,162)", "rgbChannel": "16 185 129", "hslChannel": "160.1 84.1% 39.4%" }, @@ -1060,6 +1189,7 @@ "hex": "#059669", "rgb": "rgb(5,150,105)", "hsl": "hsl(161.4,93.5%,30.4%)", + "oklch": "oklch(0.60,0.13,163)", "rgbChannel": "5 150 105", "hslChannel": "161.4 93.5% 30.4%" }, @@ -1068,6 +1198,7 @@ "hex": "#047857", "rgb": "rgb(4,120,87)", "hsl": "hsl(162.9,93.5%,24.3%)", + "oklch": "oklch(0.51,0.10,166)", "rgbChannel": "4 120 87", "hslChannel": "162.9 93.5% 24.3%" }, @@ -1076,6 +1207,7 @@ "hex": "#065f46", "rgb": "rgb(6,95,70)", "hsl": "hsl(163.1,88.1%,19.8%)", + "oklch": "oklch(0.43,0.09,167)", "rgbChannel": "6 95 70", "hslChannel": "163.1 88.1% 19.8%" }, @@ -1084,6 +1216,7 @@ "hex": "#064e3b", "rgb": "rgb(6,78,59)", "hsl": "hsl(164.2,85.7%,16.5%)", + "oklch": "oklch(0.38,0.07,169)", "rgbChannel": "6 78 59", "hslChannel": "164.2 85.7% 16.5%" }, @@ -1092,6 +1225,7 @@ "hex": "#022c22", "rgb": "rgb(2,44,34)", "hsl": "hsl(165.7,91.3%,9%)", + "oklch": "oklch(0.26,0.05,173)", "rgbChannel": "2 44 34", "hslChannel": "165.7 91.3% 9%" } @@ -1102,6 +1236,7 @@ "hex": "#f0fdfa", "rgb": "rgb(240,253,250)", "hsl": "hsl(166.2,76.5%,96.7%)", + "oklch": "oklch(0.98,0.01,181)", "rgbChannel": "240 253 250", "hslChannel": "166.2 76.5% 96.7%" }, @@ -1110,6 +1245,7 @@ "hex": "#ccfbf1", "rgb": "rgb(204,251,241)", "hsl": "hsl(167.2,85.5%,89.2%)", + "oklch": "oklch(0.95,0.05,181)", "rgbChannel": "204 251 241", "hslChannel": "167.2 85.5% 89.2%" }, @@ -1118,6 +1254,7 @@ "hex": "#99f6e4", "rgb": "rgb(153,246,228)", "hsl": "hsl(168.4,83.8%,78.2%)", + "oklch": "oklch(0.91,0.09,180)", "rgbChannel": "153 246 228", "hslChannel": "168.4 83.8% 78.2%" }, @@ -1126,6 +1263,7 @@ "hex": "#5eead4", "rgb": "rgb(94,234,212)", "hsl": "hsl(170.6,76.9%,64.3%)", + "oklch": "oklch(0.85,0.13,181)", "rgbChannel": "94 234 212", "hslChannel": "170.6 76.9% 64.3%" }, @@ -1134,6 +1272,7 @@ "hex": "#2dd4bf", "rgb": "rgb(45,212,191)", "hsl": "hsl(172.5,66%,50.4%)", + "oklch": "oklch(0.78,0.13,182)", "rgbChannel": "45 212 191", "hslChannel": "172.5 66% 50.4%" }, @@ -1142,6 +1281,7 @@ "hex": "#14b8a6", "rgb": "rgb(20,184,166)", "hsl": "hsl(173.4,80.4%,40%)", + "oklch": "oklch(0.70,0.12,183)", "rgbChannel": "20 184 166", "hslChannel": "173.4 80.4% 40%" }, @@ -1150,6 +1290,7 @@ "hex": "#0d9488", "rgb": "rgb(13,148,136)", "hsl": "hsl(174.7,83.9%,31.6%)", + "oklch": "oklch(0.60,0.10,185)", "rgbChannel": "13 148 136", "hslChannel": "174.7 83.9% 31.6%" }, @@ -1158,6 +1299,7 @@ "hex": "#0f766e", "rgb": "rgb(15,118,110)", "hsl": "hsl(175.3,77.4%,26.1%)", + "oklch": "oklch(0.51,0.09,186)", "rgbChannel": "15 118 110", "hslChannel": "175.3 77.4% 26.1%" }, @@ -1166,6 +1308,7 @@ "hex": "#115e59", "rgb": "rgb(17,94,89)", "hsl": "hsl(176.1,69.4%,21.8%)", + "oklch": "oklch(0.44,0.07,188)", "rgbChannel": "17 94 89", "hslChannel": "176.1 69.4% 21.8%" }, @@ -1174,6 +1317,7 @@ "hex": "#134e4a", "rgb": "rgb(19,78,74)", "hsl": "hsl(175.9,60.8%,19%)", + "oklch": "oklch(0.39,0.06,188)", "rgbChannel": "19 78 74", "hslChannel": "175.9 60.8% 19%" }, @@ -1182,6 +1326,7 @@ "hex": "#042f2e", "rgb": "rgb(4,47,46)", "hsl": "hsl(178.6,84.3%,10%)", + "oklch": "oklch(0.28,0.04,193)", "rgbChannel": "4 47 46", "hslChannel": "178.6 84.3% 10%" } @@ -1192,6 +1337,7 @@ "hex": "#ecfeff", "rgb": "rgb(236,254,255)", "hsl": "hsl(183.2,100%,96.3%)", + "oklch": "oklch(0.98,0.02,201)", "rgbChannel": "236 254 255", "hslChannel": "183.2 100% 96.3%" }, @@ -1200,6 +1346,7 @@ "hex": "#cffafe", "rgb": "rgb(207,250,254)", "hsl": "hsl(185.1,95.9%,90.4%)", + "oklch": "oklch(0.96,0.04,203)", "rgbChannel": "207 250 254", "hslChannel": "185.1 95.9% 90.4%" }, @@ -1208,6 +1355,7 @@ "hex": "#a5f3fc", "rgb": "rgb(165,243,252)", "hsl": "hsl(186.2,93.5%,81.8%)", + "oklch": "oklch(0.92,0.08,205)", "rgbChannel": "165 243 252", "hslChannel": "186.2 93.5% 81.8%" }, @@ -1216,6 +1364,7 @@ "hex": "#67e8f9", "rgb": "rgb(103,232,249)", "hsl": "hsl(187,92.4%,69%)", + "oklch": "oklch(0.87,0.12,207)", "rgbChannel": "103 232 249", "hslChannel": "187 92.4% 69%" }, @@ -1224,6 +1373,7 @@ "hex": "#22d3ee", "rgb": "rgb(34,211,238)", "hsl": "hsl(187.9,85.7%,53.3%)", + "oklch": "oklch(0.80,0.13,212)", "rgbChannel": "34 211 238", "hslChannel": "187.9 85.7% 53.3%" }, @@ -1232,6 +1382,7 @@ "hex": "#06b6d4", "rgb": "rgb(6,182,212)", "hsl": "hsl(188.7,94.5%,42.7%)", + "oklch": "oklch(0.71,0.13,215)", "rgbChannel": "6 182 212", "hslChannel": "188.7 94.5% 42.7%" }, @@ -1240,6 +1391,7 @@ "hex": "#0891b2", "rgb": "rgb(8,145,178)", "hsl": "hsl(191.6,91.4%,36.5%)", + "oklch": "oklch(0.61,0.11,222)", "rgbChannel": "8 145 178", "hslChannel": "191.6 91.4% 36.5%" }, @@ -1248,6 +1400,7 @@ "hex": "#0e7490", "rgb": "rgb(14,116,144)", "hsl": "hsl(192.9,82.3%,31%)", + "oklch": "oklch(0.52,0.09,223)", "rgbChannel": "14 116 144", "hslChannel": "192.9 82.3% 31%" }, @@ -1256,6 +1409,7 @@ "hex": "#155e75", "rgb": "rgb(21,94,117)", "hsl": "hsl(194.4,69.6%,27.1%)", + "oklch": "oklch(0.45,0.08,224)", "rgbChannel": "21 94 117", "hslChannel": "194.4 69.6% 27.1%" }, @@ -1264,6 +1418,7 @@ "hex": "#164e63", "rgb": "rgb(22,78,99)", "hsl": "hsl(196.4,63.6%,23.7%)", + "oklch": "oklch(0.40,0.07,227)", "rgbChannel": "22 78 99", "hslChannel": "196.4 63.6% 23.7%" }, @@ -1272,6 +1427,7 @@ "hex": "#083344", "rgb": "rgb(8,51,68)", "hsl": "hsl(197,78.9%,14.9%)", + "oklch": "oklch(0.30,0.05,230)", "rgbChannel": "8 51 68", "hslChannel": "197 78.9% 14.9%" } @@ -1282,6 +1438,7 @@ "hex": "#f0f9ff", "rgb": "rgb(240,249,255)", "hsl": "hsl(204,100%,97.1%)", + "oklch": "oklch(0.98,0.01,237)", "rgbChannel": "240 249 255", "hslChannel": "204 100% 97.1%" }, @@ -1290,6 +1447,7 @@ "hex": "#e0f2fe", "rgb": "rgb(224,242,254)", "hsl": "hsl(204,93.8%,93.7%)", + "oklch": "oklch(0.95,0.03,237)", "rgbChannel": "224 242 254", "hslChannel": "204 93.8% 93.7%" }, @@ -1298,6 +1456,7 @@ "hex": "#bae6fd", "rgb": "rgb(186,230,253)", "hsl": "hsl(200.6,94.4%,86.1%)", + "oklch": "oklch(0.90,0.06,231)", "rgbChannel": "186 230 253", "hslChannel": "200.6 94.4% 86.1%" }, @@ -1306,6 +1465,7 @@ "hex": "#7dd3fc", "rgb": "rgb(125,211,252)", "hsl": "hsl(199.4,95.5%,73.9%)", + "oklch": "oklch(0.83,0.10,230)", "rgbChannel": "125 211 252", "hslChannel": "199.4 95.5% 73.9%" }, @@ -1314,6 +1474,7 @@ "hex": "#38bdf8", "rgb": "rgb(56,189,248)", "hsl": "hsl(198.4,93.2%,59.6%)", + "oklch": "oklch(0.75,0.14,233)", "rgbChannel": "56 189 248", "hslChannel": "198.4 93.2% 59.6%" }, @@ -1322,6 +1483,7 @@ "hex": "#0ea5e9", "rgb": "rgb(14,165,233)", "hsl": "hsl(198.6,88.7%,48.4%)", + "oklch": "oklch(0.68,0.15,237)", "rgbChannel": "14 165 233", "hslChannel": "198.6 88.7% 48.4%" }, @@ -1330,6 +1492,7 @@ "hex": "#0284c7", "rgb": "rgb(2,132,199)", "hsl": "hsl(200.4,98%,39.4%)", + "oklch": "oklch(0.59,0.14,242)", "rgbChannel": "2 132 199", "hslChannel": "200.4 98% 39.4%" }, @@ -1338,6 +1501,7 @@ "hex": "#0369a1", "rgb": "rgb(3,105,161)", "hsl": "hsl(201.3,96.3%,32.2%)", + "oklch": "oklch(0.50,0.12,243)", "rgbChannel": "3 105 161", "hslChannel": "201.3 96.3% 32.2%" }, @@ -1346,6 +1510,7 @@ "hex": "#075985", "rgb": "rgb(7,89,133)", "hsl": "hsl(201,90%,27.5%)", + "oklch": "oklch(0.44,0.10,241)", "rgbChannel": "7 89 133", "hslChannel": "201 90% 27.5%" }, @@ -1354,6 +1519,7 @@ "hex": "#0c4a6e", "rgb": "rgb(12,74,110)", "hsl": "hsl(202,80.3%,23.9%)", + "oklch": "oklch(0.39,0.08,241)", "rgbChannel": "12 74 110", "hslChannel": "202 80.3% 23.9%" }, @@ -1362,6 +1528,7 @@ "hex": "#082f49", "rgb": "rgb(8,47,73)", "hsl": "hsl(204,80.2%,15.9%)", + "oklch": "oklch(0.29,0.06,243)", "rgbChannel": "8 47 73", "hslChannel": "204 80.2% 15.9%" } @@ -1372,6 +1539,7 @@ "hex": "#eff6ff", "rgb": "rgb(239,246,255)", "hsl": "hsl(213.8,100%,96.9%)", + "oklch": "oklch(0.97,0.01,255)", "rgbChannel": "239 246 255", "hslChannel": "213.8 100% 96.9%" }, @@ -1380,6 +1548,7 @@ "hex": "#dbeafe", "rgb": "rgb(219,234,254)", "hsl": "hsl(214.3,94.6%,92.7%)", + "oklch": "oklch(0.93,0.03,256)", "rgbChannel": "219 234 254", "hslChannel": "214.3 94.6% 92.7%" }, @@ -1388,6 +1557,7 @@ "hex": "#bfdbfe", "rgb": "rgb(191,219,254)", "hsl": "hsl(213.3,96.9%,87.3%)", + "oklch": "oklch(0.88,0.06,254)", "rgbChannel": "191 219 254", "hslChannel": "213.3 96.9% 87.3%" }, @@ -1396,6 +1566,7 @@ "hex": "#93c5fd", "rgb": "rgb(147,197,253)", "hsl": "hsl(211.7,96.4%,78.4%)", + "oklch": "oklch(0.81,0.10,252)", "rgbChannel": "147 197 253", "hslChannel": "211.7 96.4% 78.4%" }, @@ -1404,6 +1575,7 @@ "hex": "#60a5fa", "rgb": "rgb(96,165,250)", "hsl": "hsl(213.1,93.9%,67.8%)", + "oklch": "oklch(0.71,0.14,255)", "rgbChannel": "96 165 250", "hslChannel": "213.1 93.9% 67.8%" }, @@ -1412,6 +1584,7 @@ "hex": "#3b82f6", "rgb": "rgb(59,130,246)", "hsl": "hsl(217.2,91.2%,59.8%)", + "oklch": "oklch(0.62,0.19,260)", "rgbChannel": "59 130 246", "hslChannel": "217.2 91.2% 59.8%" }, @@ -1420,6 +1593,7 @@ "hex": "#2563eb", "rgb": "rgb(37,99,235)", "hsl": "hsl(221.2,83.2%,53.3%)", + "oklch": "oklch(0.55,0.22,263)", "rgbChannel": "37 99 235", "hslChannel": "221.2 83.2% 53.3%" }, @@ -1428,6 +1602,7 @@ "hex": "#1d4ed8", "rgb": "rgb(29,78,216)", "hsl": "hsl(224.3,76.3%,48%)", + "oklch": "oklch(0.49,0.22,264)", "rgbChannel": "29 78 216", "hslChannel": "224.3 76.3% 48%" }, @@ -1436,6 +1611,7 @@ "hex": "#1e40af", "rgb": "rgb(30,64,175)", "hsl": "hsl(225.9,70.7%,40.2%)", + "oklch": "oklch(0.42,0.18,266)", "rgbChannel": "30 64 175", "hslChannel": "225.9 70.7% 40.2%" }, @@ -1444,6 +1620,7 @@ "hex": "#1e3a8a", "rgb": "rgb(30,58,138)", "hsl": "hsl(224.4,64.3%,32.9%)", + "oklch": "oklch(0.38,0.14,266)", "rgbChannel": "30 58 138", "hslChannel": "224.4 64.3% 32.9%" }, @@ -1452,6 +1629,7 @@ "hex": "#172554", "rgb": "rgb(23,37,84)", "hsl": "hsl(226.2,57%,21%)", + "oklch": "oklch(0.28,0.09,268)", "rgbChannel": "23 37 84", "hslChannel": "226.2 57% 21%" } @@ -1462,6 +1640,7 @@ "hex": "#eef2ff", "rgb": "rgb(238,242,255)", "hsl": "hsl(225.9,100%,96.7%)", + "oklch": "oklch(0.96,0.02,272)", "rgbChannel": "238 242 255", "hslChannel": "225.9 100% 96.7%" }, @@ -1470,6 +1649,7 @@ "hex": "#e0e7ff", "rgb": "rgb(224,231,255)", "hsl": "hsl(226.5,100%,93.9%)", + "oklch": "oklch(0.93,0.03,273)", "rgbChannel": "224 231 255", "hslChannel": "226.5 100% 93.9%" }, @@ -1478,6 +1658,7 @@ "hex": "#c7d2fe", "rgb": "rgb(199,210,254)", "hsl": "hsl(228,96.5%,88.8%)", + "oklch": "oklch(0.87,0.06,274)", "rgbChannel": "199 210 254", "hslChannel": "228 96.5% 88.8%" }, @@ -1486,6 +1667,7 @@ "hex": "#a5b4fc", "rgb": "rgb(165,180,252)", "hsl": "hsl(229.7,93.5%,81.8%)", + "oklch": "oklch(0.79,0.10,275)", "rgbChannel": "165 180 252", "hslChannel": "229.7 93.5% 81.8%" }, @@ -1494,6 +1676,7 @@ "hex": "#818cf8", "rgb": "rgb(129,140,248)", "hsl": "hsl(234.5,89.5%,73.9%)", + "oklch": "oklch(0.68,0.16,277)", "rgbChannel": "129 140 248", "hslChannel": "234.5 89.5% 73.9%" }, @@ -1502,6 +1685,7 @@ "hex": "#6366f1", "rgb": "rgb(99,102,241)", "hsl": "hsl(238.7,83.5%,66.7%)", + "oklch": "oklch(0.59,0.20,277)", "rgbChannel": "99 102 241", "hslChannel": "238.7 83.5% 66.7%" }, @@ -1510,6 +1694,7 @@ "hex": "#4f46e5", "rgb": "rgb(79,70,229)", "hsl": "hsl(243.4,75.4%,58.6%)", + "oklch": "oklch(0.51,0.23,277)", "rgbChannel": "79 70 229", "hslChannel": "243.4 75.4% 58.6%" }, @@ -1518,6 +1703,7 @@ "hex": "#4338ca", "rgb": "rgb(67,56,202)", "hsl": "hsl(244.5,57.9%,50.6%)", + "oklch": "oklch(0.46,0.21,277)", "rgbChannel": "67 56 202", "hslChannel": "244.5 57.9% 50.6%" }, @@ -1526,6 +1712,7 @@ "hex": "#3730a3", "rgb": "rgb(55,48,163)", "hsl": "hsl(243.7,54.5%,41.4%)", + "oklch": "oklch(0.40,0.18,277)", "rgbChannel": "55 48 163", "hslChannel": "243.7 54.5% 41.4%" }, @@ -1534,6 +1721,7 @@ "hex": "#312e81", "rgb": "rgb(49,46,129)", "hsl": "hsl(242.2,47.4%,34.3%)", + "oklch": "oklch(0.36,0.14,279)", "rgbChannel": "49 46 129", "hslChannel": "242.2 47.4% 34.3%" }, @@ -1542,6 +1730,7 @@ "hex": "#1e1b4b", "rgb": "rgb(30,27,75)", "hsl": "hsl(243.8,47.1%,20%)", + "oklch": "oklch(0.26,0.09,281)", "rgbChannel": "30 27 75", "hslChannel": "243.8 47.1% 20%" } @@ -1552,6 +1741,7 @@ "hex": "#f5f3ff", "rgb": "rgb(245,243,255)", "hsl": "hsl(250,100%,97.6%)", + "oklch": "oklch(0.97,0.02,294)", "rgbChannel": "245 243 255", "hslChannel": "250 100% 97.6%" }, @@ -1560,6 +1750,7 @@ "hex": "#ede9fe", "rgb": "rgb(237,233,254)", "hsl": "hsl(251.4,91.3%,95.5%)", + "oklch": "oklch(0.94,0.03,295)", "rgbChannel": "237 233 254", "hslChannel": "251.4 91.3% 95.5%" }, @@ -1568,6 +1759,7 @@ "hex": "#ddd6fe", "rgb": "rgb(221,214,254)", "hsl": "hsl(250.5,95.2%,91.8%)", + "oklch": "oklch(0.89,0.05,293)", "rgbChannel": "221 214 254", "hslChannel": "250.5 95.2% 91.8%" }, @@ -1576,6 +1768,7 @@ "hex": "#c4b5fd", "rgb": "rgb(196,181,253)", "hsl": "hsl(252.5,94.7%,85.1%)", + "oklch": "oklch(0.81,0.10,294)", "rgbChannel": "196 181 253", "hslChannel": "252.5 94.7% 85.1%" }, @@ -1584,6 +1777,7 @@ "hex": "#a78bfa", "rgb": "rgb(167,139,250)", "hsl": "hsl(255.1,91.7%,76.3%)", + "oklch": "oklch(0.71,0.16,294)", "rgbChannel": "167 139 250", "hslChannel": "255.1 91.7% 76.3%" }, @@ -1592,6 +1786,7 @@ "hex": "#8b5cf6", "rgb": "rgb(139,92,246)", "hsl": "hsl(258.3,89.5%,66.3%)", + "oklch": "oklch(0.61,0.22,293)", "rgbChannel": "139 92 246", "hslChannel": "258.3 89.5% 66.3%" }, @@ -1600,6 +1795,7 @@ "hex": "#7c3aed", "rgb": "rgb(124,58,237)", "hsl": "hsl(262.1,83.3%,57.8%)", + "oklch": "oklch(0.54,0.25,293)", "rgbChannel": "124 58 237", "hslChannel": "262.1 83.3% 57.8%" }, @@ -1608,6 +1804,7 @@ "hex": "#6d28d9", "rgb": "rgb(109,40,217)", "hsl": "hsl(263.4,70%,50.4%)", + "oklch": "oklch(0.49,0.24,293)", "rgbChannel": "109 40 217", "hslChannel": "263.4 70% 50.4%" }, @@ -1616,6 +1813,7 @@ "hex": "#5b21b6", "rgb": "rgb(91,33,182)", "hsl": "hsl(263.4,69.3%,42.2%)", + "oklch": "oklch(0.43,0.21,293)", "rgbChannel": "91 33 182", "hslChannel": "263.4 69.3% 42.2%" }, @@ -1624,6 +1822,7 @@ "hex": "#4c1d95", "rgb": "rgb(76,29,149)", "hsl": "hsl(263.5,67.4%,34.9%)", + "oklch": "oklch(0.38,0.18,294)", "rgbChannel": "76 29 149", "hslChannel": "263.5 67.4% 34.9%" }, @@ -1632,6 +1831,7 @@ "hex": "#1e1b4b", "rgb": "rgb(46,16,101)", "hsl": "hsl(261.2,72.6%,22.9%)", + "oklch": "oklch(0.28,0.14,291)", "rgbChannel": "46 16 101", "hslChannel": "261.2 72.6% 22.9%" } @@ -1642,6 +1842,7 @@ "hex": "#faf5ff", "rgb": "rgb(250,245,255)", "hsl": "hsl(270,100%,98%)", + "oklch": "oklch(0.98,0.01,308)", "rgbChannel": "250 245 255", "hslChannel": "270 100% 98%" }, @@ -1650,6 +1851,7 @@ "hex": "#f3e8ff", "rgb": "rgb(243,232,255)", "hsl": "hsl(268.7,100%,95.5%)", + "oklch": "oklch(0.95,0.03,307)", "rgbChannel": "243 232 255", "hslChannel": "268.7 100% 95.5%" }, @@ -1658,6 +1860,7 @@ "hex": "#e9d5ff", "rgb": "rgb(233,213,255)", "hsl": "hsl(268.6,100%,91.8%)", + "oklch": "oklch(0.90,0.06,307)", "rgbChannel": "233 213 255", "hslChannel": "268.6 100% 91.8%" }, @@ -1666,6 +1869,7 @@ "hex": "#d8b4fe", "rgb": "rgb(216,180,254)", "hsl": "hsl(269.2,97.4%,85.1%)", + "oklch": "oklch(0.83,0.11,306)", "rgbChannel": "216 180 254", "hslChannel": "269.2 97.4% 85.1%" }, @@ -1674,6 +1878,7 @@ "hex": "#c084fc", "rgb": "rgb(192,132,252)", "hsl": "hsl(270,95.2%,75.3%)", + "oklch": "oklch(0.72,0.18,306)", "rgbChannel": "192 132 252", "hslChannel": "270 95.2% 75.3%" }, @@ -1682,6 +1887,7 @@ "hex": "#a855f7", "rgb": "rgb(168,85,247)", "hsl": "hsl(270.7,91%,65.1%)", + "oklch": "oklch(0.63,0.23,304)", "rgbChannel": "168 85 247", "hslChannel": "270.7 91% 65.1%" }, @@ -1690,6 +1896,7 @@ "hex": "#9333ea", "rgb": "rgb(147,51,234)", "hsl": "hsl(271.5,81.3%,55.9%)", + "oklch": "oklch(0.56,0.25,302)", "rgbChannel": "147 51 234", "hslChannel": "271.5 81.3% 55.9%" }, @@ -1698,6 +1905,7 @@ "hex": "#7e22ce", "rgb": "rgb(126,34,206)", "hsl": "hsl(272.1,71.7%,47.1%)", + "oklch": "oklch(0.50,0.24,302)", "rgbChannel": "126 34 206", "hslChannel": "272.1 71.7% 47.1%" }, @@ -1706,6 +1914,7 @@ "hex": "#6b21a8", "rgb": "rgb(107,33,168)", "hsl": "hsl(272.9,67.2%,39.4%)", + "oklch": "oklch(0.44,0.20,304)", "rgbChannel": "107 33 168", "hslChannel": "272.9 67.2% 39.4%" }, @@ -1714,6 +1923,7 @@ "hex": "#581c87", "rgb": "rgb(88,28,135)", "hsl": "hsl(273.6,65.6%,32%)", + "oklch": "oklch(0.38,0.17,305)", "rgbChannel": "88 28 135", "hslChannel": "273.6 65.6% 32%" }, @@ -1722,6 +1932,7 @@ "hex": "#3b0764", "rgb": "rgb(59,7,100)", "hsl": "hsl(273.5,86.9%,21%)", + "oklch": "oklch(0.29,0.14,303)", "rgbChannel": "59 7 100", "hslChannel": "273.5 86.9% 21%" } @@ -1732,6 +1943,7 @@ "hex": "#fdf4ff", "rgb": "rgb(253,244,255)", "hsl": "hsl(289.1,100%,97.8%)", + "oklch": "oklch(0.98,0.02,320)", "rgbChannel": "253 244 255", "hslChannel": "289.1 100% 97.8%" }, @@ -1740,6 +1952,7 @@ "hex": "#fae8ff", "rgb": "rgb(250,232,255)", "hsl": "hsl(287,100%,95.5%)", + "oklch": "oklch(0.95,0.04,319)", "rgbChannel": "250 232 255", "hslChannel": "287 100% 95.5%" }, @@ -1748,6 +1961,7 @@ "hex": "#f5d0fe", "rgb": "rgb(245,208,254)", "hsl": "hsl(288.3,95.8%,90.6%)", + "oklch": "oklch(0.90,0.07,320)", "rgbChannel": "245 208 254", "hslChannel": "288.3 95.8% 90.6%" }, @@ -1756,6 +1970,7 @@ "hex": "#f0abfc", "rgb": "rgb(240,171,252)", "hsl": "hsl(291.1,93.1%,82.9%)", + "oklch": "oklch(0.83,0.13,321)", "rgbChannel": "240 171 252", "hslChannel": "291.1 93.1% 82.9%" }, @@ -1764,6 +1979,7 @@ "hex": "#e879f9", "rgb": "rgb(232,121,249)", "hsl": "hsl(292,91.4%,72.5%)", + "oklch": "oklch(0.75,0.21,322)", "rgbChannel": "232 121 249", "hslChannel": "292 91.4% 72.5%" }, @@ -1772,6 +1988,7 @@ "hex": "#d946ef", "rgb": "rgb(217,70,239)", "hsl": "hsl(292.2,84.1%,60.6%)", + "oklch": "oklch(0.67,0.26,322)", "rgbChannel": "217 70 239", "hslChannel": "292.2 84.1% 60.6%" }, @@ -1780,6 +1997,7 @@ "hex": "#c026d3", "rgb": "rgb(192,38,211)", "hsl": "hsl(293.4,69.5%,48.8%)", + "oklch": "oklch(0.59,0.26,323)", "rgbChannel": "192 38 211", "hslChannel": "293.4 69.5% 48.8%" }, @@ -1788,6 +2006,7 @@ "hex": "#a21caf", "rgb": "rgb(162,28,175)", "hsl": "hsl(294.7,72.4%,39.8%)", + "oklch": "oklch(0.52,0.23,324)", "rgbChannel": "162 28 175", "hslChannel": "294.7 72.4% 39.8%" }, @@ -1796,6 +2015,7 @@ "hex": "#86198f", "rgb": "rgb(134,25,143)", "hsl": "hsl(295.4,70.2%,32.9%)", + "oklch": "oklch(0.45,0.19,325)", "rgbChannel": "134 25 143", "hslChannel": "295.4 70.2% 32.9%" }, @@ -1804,6 +2024,7 @@ "hex": "#701a75", "rgb": "rgb(112,26,117)", "hsl": "hsl(296.7,63.6%,28%)", + "oklch": "oklch(0.40,0.16,326)", "rgbChannel": "112 26 117", "hslChannel": "296.7 63.6% 28%" }, @@ -1812,6 +2033,7 @@ "hex": "#4a044e", "rgb": "rgb(74,4,78)", "hsl": "hsl(296.8,90.2%,16.1%)", + "oklch": "oklch(0.29,0.13,326)", "rgbChannel": "74 4 78", "hslChannel": "296.8 90.2% 16.1%" } @@ -1822,6 +2044,7 @@ "hex": "#fdf2f8", "rgb": "rgb(253,242,248)", "hsl": "hsl(327.3,73.3%,97.1%)", + "oklch": "oklch(0.97,0.01,343)", "rgbChannel": "253 242 248", "hslChannel": "327.3 73.3% 97.1%" }, @@ -1830,6 +2053,7 @@ "hex": "#fce7f3", "rgb": "rgb(252,231,243)", "hsl": "hsl(325.7,77.8%,94.7%)", + "oklch": "oklch(0.95,0.03,342)", "rgbChannel": "252 231 243", "hslChannel": "325.7 77.8% 94.7%" }, @@ -1838,6 +2062,7 @@ "hex": "#fbcfe8", "rgb": "rgb(251,207,232)", "hsl": "hsl(325.9,84.6%,89.8%)", + "oklch": "oklch(0.90,0.06,343)", "rgbChannel": "251 207 232", "hslChannel": "325.9 84.6% 89.8%" }, @@ -1846,6 +2071,7 @@ "hex": "#f9a8d4", "rgb": "rgb(249,168,212)", "hsl": "hsl(327.4,87.1%,81.8%)", + "oklch": "oklch(0.82,0.11,346)", "rgbChannel": "249 168 212", "hslChannel": "327.4 87.1% 81.8%" }, @@ -1854,6 +2080,7 @@ "hex": "#f472b6", "rgb": "rgb(244,114,182)", "hsl": "hsl(328.6,85.5%,70.2%)", + "oklch": "oklch(0.73,0.18,350)", "rgbChannel": "244 114 182", "hslChannel": "328.6 85.5% 70.2%" }, @@ -1862,6 +2089,7 @@ "hex": "#ec4899", "rgb": "rgb(236,72,153)", "hsl": "hsl(330.4,81.2%,60.4%)", + "oklch": "oklch(0.66,0.21,354)", "rgbChannel": "236 72 153", "hslChannel": "330.4 81.2% 60.4%" }, @@ -1870,6 +2098,7 @@ "hex": "#db2777", "rgb": "rgb(219,39,119)", "hsl": "hsl(333.3,71.4%,50.6%)", + "oklch": "oklch(0.59,0.22,1)", "rgbChannel": "219 39 119", "hslChannel": "333.3 71.4% 50.6%" }, @@ -1878,6 +2107,7 @@ "hex": "#be185d", "rgb": "rgb(190,24,93)", "hsl": "hsl(335.1,77.6%,42%)", + "oklch": "oklch(0.52,0.20,4)", "rgbChannel": "190 24 93", "hslChannel": "335.1 77.6% 42%" }, @@ -1886,6 +2116,7 @@ "hex": "#9d174d", "rgb": "rgb(157,23,77)", "hsl": "hsl(335.8,74.4%,35.3%)", + "oklch": "oklch(0.46,0.17,4)", "rgbChannel": "157 23 77", "hslChannel": "335.8 74.4% 35.3%" }, @@ -1894,6 +2125,7 @@ "hex": "#831843", "rgb": "rgb(131,24,67)", "hsl": "hsl(335.9,69%,30.4%)", + "oklch": "oklch(0.41,0.14,2)", "rgbChannel": "131 24 67", "hslChannel": "335.9 69% 30.4%" }, @@ -1902,6 +2134,7 @@ "hex": "#500724", "rgb": "rgb(80,7,36)", "hsl": "hsl(336.2,83.9%,17.1%)", + "oklch": "oklch(0.28,0.10,4)", "rgbChannel": "80 7 36", "hslChannel": "336.2 83.9% 17.1%" } @@ -1912,6 +2145,7 @@ "hex": "#fff1f2", "rgb": "rgb(255,241,242)", "hsl": "hsl(355.7,100%,97.3%)", + "oklch": "oklch(0.97,0.02,12)", "rgbChannel": "255 241 242", "hslChannel": "355.7 100% 97.3%" }, @@ -1920,6 +2154,7 @@ "hex": "#ffe4e6", "rgb": "rgb(255,228,230)", "hsl": "hsl(355.6,100%,94.7%)", + "oklch": "oklch(0.94,0.03,13)", "rgbChannel": "255 228 230", "hslChannel": "355.6 100% 94.7%" }, @@ -1928,6 +2163,7 @@ "hex": "#fecdd3", "rgb": "rgb(254,205,211)", "hsl": "hsl(352.7,96.1%,90%)", + "oklch": "oklch(0.89,0.06,10)", "rgbChannel": "254 205 211", "hslChannel": "352.7 96.1% 90%" }, @@ -1936,6 +2172,7 @@ "hex": "#fda4af", "rgb": "rgb(253,164,175)", "hsl": "hsl(352.6,95.7%,81.8%)", + "oklch": "oklch(0.81,0.11,12)", "rgbChannel": "253 164 175", "hslChannel": "352.6 95.7% 81.8%" }, @@ -1944,6 +2181,7 @@ "hex": "#fb7185", "rgb": "rgb(251,113,133)", "hsl": "hsl(351.3,94.5%,71.4%)", + "oklch": "oklch(0.72,0.17,13)", "rgbChannel": "251 113 133", "hslChannel": "351.3 94.5% 71.4%" }, @@ -1952,6 +2190,7 @@ "hex": "#f43f5e", "rgb": "rgb(244,63,94)", "hsl": "hsl(349.7,89.2%,60.2%)", + "oklch": "oklch(0.65,0.22,16)", "rgbChannel": "244 63 94", "hslChannel": "349.7 89.2% 60.2%" }, @@ -1960,6 +2199,7 @@ "hex": "#e11d48", "rgb": "rgb(225,29,72)", "hsl": "hsl(346.8,77.2%,49.8%)", + "oklch": "oklch(0.59,0.22,18)", "rgbChannel": "225 29 72", "hslChannel": "346.8 77.2% 49.8%" }, @@ -1968,6 +2208,7 @@ "hex": "#be123c", "rgb": "rgb(190,18,60)", "hsl": "hsl(345.3,82.7%,40.8%)", + "oklch": "oklch(0.51,0.20,17)", "rgbChannel": "190 18 60", "hslChannel": "345.3 82.7% 40.8%" }, @@ -1976,6 +2217,7 @@ "hex": "#9f1239", "rgb": "rgb(159,18,57)", "hsl": "hsl(343.4,79.7%,34.7%)", + "oklch": "oklch(0.45,0.17,14)", "rgbChannel": "159 18 57", "hslChannel": "343.4 79.7% 34.7%" }, @@ -1984,6 +2226,7 @@ "hex": "#881337", "rgb": "rgb(136,19,55)", "hsl": "hsl(341.5,75.5%,30.4%)", + "oklch": "oklch(0.41,0.15,10)", "rgbChannel": "136 19 55", "hslChannel": "341.5 75.5% 30.4%" }, @@ -1992,6 +2235,7 @@ "hex": "#4c0519", "rgb": "rgb(76,5,25)", "hsl": "hsl(343.1,87.7%,15.9%)", + "oklch": "oklch(0.27,0.10,12)", "rgbChannel": "76 5 25", "hslChannel": "343.1 87.7% 15.9%" } diff --git a/apps/www/public/r/styles/default/stepper-demo.json b/apps/www/public/r/styles/default/stepper-demo.json index fc5b662b963..6a48974d5c2 100644 --- a/apps/www/public/r/styles/default/stepper-demo.json +++ b/apps/www/public/r/styles/default/stepper-demo.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-demo.tsx", - "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n
    \n ))}\n
    \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n
    \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n return (\n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-description.json b/apps/www/public/r/styles/default/stepper-description.json index 6a110c6f99d..4cfbf2201b0 100644 --- a/apps/www/public/r/styles/default/stepper-description.json +++ b/apps/www/public/r/styles/default/stepper-description.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-description.tsx", - "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n description: \"This is the first step\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n description: \"This is the second step\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n description: \"This is the third step\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {step.description}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n description: \"This is the first step\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n description: \"This is the second step\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n description: \"This is the third step\",\n }\n)\n\nexport default function StepperDemo() {\n return (\n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {step.description}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-form.json b/apps/www/public/r/styles/default/stepper-form.json index 5d6a43cce8d..4f6a19a2c84 100644 --- a/apps/www/public/r/styles/default/stepper-form.json +++ b/apps/www/public/r/styles/default/stepper-form.json @@ -10,7 +10,7 @@ "files": [ { "path": "examples/stepper-form.tsx", - "content": "import { zodResolver } from \"@hookform/resolvers/zod\"\nimport { useForm, useFormContext } from \"react-hook-form\"\nimport { z } from \"zod\"\n\nimport { Form } from \"@/registry/default/ui/form\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst shippingSchema = z.object({\n address: z.string().min(1, \"Address is required\"),\n city: z.string().min(1, \"City is required\"),\n postalCode: z.string().min(5, \"Postal code is required\"),\n})\n\nconst paymentSchema = z.object({\n cardNumber: z.string().min(16, \"Card number is required\"),\n expirationDate: z.string().min(5, \"Expiration date is required\"),\n cvv: z.string().min(3, \"CVV is required\"),\n})\n\ntype ShippingFormValues = z.infer\ntype PaymentFormValues = z.infer\n\nconst stepperInstance = defineStepper(\n {\n id: \"shipping\",\n title: \"Shipping\",\n schema: shippingSchema,\n },\n {\n id: \"payment\",\n title: \"Payment\",\n schema: paymentSchema,\n },\n {\n id: \"complete\",\n title: \"Complete\",\n schema: z.object({}),\n }\n)\n\nexport default function StepperForm() {\n return (\n \n \n \n )\n}\n\nconst FormStepperComponent = () => {\n const { steps, useStepper, utils } = stepperInstance\n const methods = useStepper()\n\n const form = useForm({\n mode: \"onTouched\",\n resolver: zodResolver(methods.current.schema),\n })\n\n const onSubmit = (values: z.infer) => {\n console.log(`Form values for step ${methods.current.id}:`, values)\n }\n\n const currentIndex = utils.getIndex(methods.current.id)\n\n return (\n
    \n \n \n {steps.map((step) => (\n {\n const valid = await form.trigger()\n if (!valid) return\n if (utils.getIndex(step.id) - currentIndex > 1) return\n methods.goTo(step.id)\n }}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n shipping: () => ,\n payment: () => ,\n complete: () => ,\n })}\n \n Previous\n {\n const valid = await form.trigger()\n if (!valid) return false\n if (utils.getIndex(nextStep.id as any) - currentIndex > 1)\n return false\n return true\n }}\n >\n Next\n \n Reset\n \n \n \n )\n}\n\nconst ShippingForm = () => {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Address\n \n \n {errors.address && (\n \n {errors.address.message}\n \n )}\n
    \n
    \n \n City\n \n \n {errors.city && (\n \n {errors.city.message}\n \n )}\n
    \n
    \n \n Postal Code\n \n \n {errors.postalCode && (\n \n {errors.postalCode.message}\n \n )}\n
    \n
    \n )\n}\n\nfunction PaymentForm() {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Card Number\n \n \n {errors.cardNumber && (\n \n {errors.cardNumber.message}\n \n )}\n
    \n
    \n \n Expiration Date\n \n \n {errors.expirationDate && (\n \n {errors.expirationDate.message}\n \n )}\n
    \n
    \n \n CVV\n \n \n {errors.cvv && (\n {errors.cvv.message}\n )}\n
    \n
    \n )\n}\n\nfunction CompleteComponent() {\n return
    Thank you! Your order is complete.
    \n}\n", + "content": "import * as React from \"react\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { useForm, useFormContext } from \"react-hook-form\"\nimport { z } from \"zod\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Form } from \"@/registry/default/ui/form\"\nimport { Input } from \"@/registry/default/ui/input\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst shippingSchema = z.object({\n address: z.string().min(1, \"Address is required\"),\n city: z.string().min(1, \"City is required\"),\n postalCode: z.string().min(5, \"Postal code is required\"),\n})\n\nconst paymentSchema = z.object({\n cardNumber: z.string().min(16, \"Card number is required\"),\n expirationDate: z.string().min(5, \"Expiration date is required\"),\n cvv: z.string().min(3, \"CVV is required\"),\n})\n\ntype ShippingFormValues = z.infer\ntype PaymentFormValues = z.infer\n\nconst ShippingForm = () => {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Address\n \n \n {errors.address && (\n \n {errors.address.message}\n \n )}\n
    \n
    \n \n City\n \n \n {errors.city && (\n \n {errors.city.message}\n \n )}\n
    \n
    \n \n Postal Code\n \n \n {errors.postalCode && (\n \n {errors.postalCode.message}\n \n )}\n
    \n
    \n )\n}\n\nfunction PaymentForm() {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Card Number\n \n \n {errors.cardNumber && (\n \n {errors.cardNumber.message}\n \n )}\n
    \n
    \n \n Expiration Date\n \n \n {errors.expirationDate && (\n \n {errors.expirationDate.message}\n \n )}\n
    \n
    \n \n CVV\n \n \n {errors.cvv && (\n {errors.cvv.message}\n )}\n
    \n
    \n )\n}\n\nfunction CompleteComponent() {\n return
    Thank you! Your order is complete.
    \n}\n\nconst stepperInstance = defineStepper(\n {\n id: \"shipping\",\n title: \"Shipping\",\n schema: shippingSchema,\n Component: ShippingForm,\n },\n {\n id: \"payment\",\n title: \"Payment\",\n schema: paymentSchema,\n Component: PaymentForm,\n },\n {\n id: \"complete\",\n title: \"Complete\",\n schema: z.object({}),\n Component: CompleteComponent,\n }\n)\n\nexport default function StepperForm() {\n return (\n \n \n \n )\n}\n\nconst FormStepperComponent = () => {\n const { useStepper } = stepperInstance\n const methods = useStepper()\n\n const form = useForm({\n mode: \"onTouched\",\n resolver: zodResolver(methods.current.schema),\n })\n\n const onSubmit = (values: z.infer) => {\n console.log(`Form values for step ${methods.current.id}:`, values)\n }\n\n return (\n
    \n \n \n {methods.all.map((step) => (\n {\n const valid = await form.trigger()\n if (!valid) return\n methods.goTo(step.id)\n }}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n shipping: ({ Component }) => ,\n payment: ({ Component }) => ,\n complete: ({ Component }) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n {\n if (methods.isLast) {\n return methods.reset()\n }\n methods.beforeNext(async () => {\n const valid = await form.trigger()\n if (!valid) return false\n return true\n })\n }}\n >\n {methods.isLast ? \"Reset\" : \"Next\"}\n \n \n \n \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-icon.json b/apps/www/public/r/styles/default/stepper-icon.json index fe6947c6478..c31631c523a 100644 --- a/apps/www/public/r/styles/default/stepper-icon.json +++ b/apps/www/public/r/styles/default/stepper-icon.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-icon.tsx", - "content": "import { HomeIcon, SettingsIcon, UserIcon } from \"lucide-react\"\n\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n icon: ,\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n icon: ,\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n icon: ,\n }\n)\n\nexport default function StepperIcon() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n icon={step.icon}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\nimport { HomeIcon, SettingsIcon, UserIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n icon: ,\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n icon: ,\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n icon: ,\n }\n)\n\nexport default function StepperDemo() {\n return (\n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n icon={step.icon}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-label-orientation.json b/apps/www/public/r/styles/default/stepper-label-orientation.json index 8a3a1ec6800..52552c2870c 100644 --- a/apps/www/public/r/styles/default/stepper-label-orientation.json +++ b/apps/www/public/r/styles/default/stepper-label-orientation.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-label-orientation.tsx", - "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/default/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/default/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\ntype LabelOrientation = \"horizontal\" | \"vertical\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [labelOrientation, setLabelOrientation] =\n React.useState(\"horizontal\")\n return (\n
    \n \n setLabelOrientation(value as LabelOrientation)\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n
    \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/default/ui/radio-group\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\ntype LabelOrientation = \"horizontal\" | \"vertical\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const [labelOrientation, setLabelOrientation] =\n React.useState(\"horizontal\")\n return (\n
    \n \n setLabelOrientation(value as LabelOrientation)\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n
    \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-responsive-variant.json b/apps/www/public/r/styles/default/stepper-responsive-variant.json index d223e5cff32..1d212773ace 100644 --- a/apps/www/public/r/styles/default/stepper-responsive-variant.json +++ b/apps/www/public/r/styles/default/stepper-responsive-variant.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-responsive-variant.tsx", - "content": "import { useMediaQuery } from \"@/hooks/use-media-query\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperResponsiveVariant() {\n const steps = stepperInstance.steps\n const isMobile = useMediaQuery(\"(max-width: 768px)\")\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {isMobile && (\n \n {({ step }) => (\n

    \n Content for {step.id}\n

    \n )}\n \n )}\n \n ))}\n
    \n {!isMobile &&\n steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { useMediaQuery } from \"@/hooks/use-media-query\"\nimport { Button } from \"@/registry/default/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperResponsiveVariant() {\n const isMobile = useMediaQuery(\"(max-width: 768px)\")\n return (\n \n {({ methods }) => (\n <>\n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {isMobile &&\n methods.when(step.id, (step) => (\n \n

    \n Content for {step.id}\n

    \n
    \n ))}\n \n ))}\n
    \n {!isMobile &&\n methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-tracking.json b/apps/www/public/r/styles/default/stepper-tracking.json index 6bc678c8290..332fb48efd2 100644 --- a/apps/www/public/r/styles/default/stepper-tracking.json +++ b/apps/www/public/r/styles/default/stepper-tracking.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-tracking.tsx", - "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/default/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/default/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n },\n {\n id: \"step-4\",\n title: \"Step 4\",\n },\n {\n id: \"step-5\",\n title: \"Step 5\",\n },\n {\n id: \"step-6\",\n title: \"Step 6\",\n }\n)\n\nexport default function StepperVerticalFollow() {\n const steps = stepperInstance.steps\n\n const [tracking, setTracking] = React.useState(false)\n return (\n
    \n setTracking(value === \"true\")}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n
    \n

    \n Content for {step.id}\n

    \n
    \n \n Previous\n Next\n Reset\n \n
    \n \n ))}\n
    \n \n )}\n \n
    \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/default/ui/radio-group\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n },\n {\n id: \"step-4\",\n title: \"Step 4\",\n },\n {\n id: \"step-5\",\n title: \"Step 5\",\n },\n {\n id: \"step-6\",\n title: \"Step 6\",\n }\n)\n\nexport default function StepperVerticalFollow() {\n const [tracking, setTracking] = React.useState(false)\n return (\n
    \n setTracking(value === \"true\")}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {methods.when(step.id, () => (\n \n
    \n

    \n Content for {step.id}\n

    \n
    \n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n {methods.isLast ? \"Reset\" : \"Next\"}\n \n \n
    \n ))}\n \n ))}\n
    \n \n )}\n \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper-variants.json b/apps/www/public/r/styles/default/stepper-variants.json index 3e5ee2c4c88..7a72008bdcd 100644 --- a/apps/www/public/r/styles/default/stepper-variants.json +++ b/apps/www/public/r/styles/default/stepper-variants.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-variants.tsx", - "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/default/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/default/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n setVariant(value as Variant)}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n\nconst VerticalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n \n ))}\n
    \n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n\nconst CircleStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n \n {methods.current.title}\n \n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n
    \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/default/ui/button\"\nimport { Label } from \"@/registry/default/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/default/ui/radio-group\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/default/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n setVariant(value as Variant)}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n \n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n\nconst VerticalStepper = () => {\n return (\n \n {({ methods }) => (\n <>\n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {methods.when(step.id, () => (\n \n

    Content for {step.id}

    \n
    \n ))}\n \n ))}\n
    \n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst CircleStepper = () => {\n return (\n \n {({ methods }) => (\n <>\n \n \n {methods.current.title}\n \n \n {methods.when(methods.current.id, () => (\n \n

    \n Content for {methods.current.id}\n

    \n
    \n ))}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/default/stepper.json b/apps/www/public/r/styles/default/stepper.json index c18aae4a179..40329946988 100644 --- a/apps/www/public/r/styles/default/stepper.json +++ b/apps/www/public/r/styles/default/stepper.json @@ -14,7 +14,7 @@ "files": [ { "path": "ui/stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n tracking?: boolean\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n labelOrientation = \"horizontal\",\n tracking = false,\n ...props\n}: StepperConfig &\n Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: { methods: Stepperize.Stepper }) => React.ReactNode)\n }) {\n const { instance } = props\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
    \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children: React.ReactNode\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      {children}
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const id = React.useId()\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n state,\n disabled,\n}: {\n isLast: boolean\n state: string\n disabled?: boolean\n} & VariantProps) => {\n if (isLast) return null\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted\",\n \"data-[state=completed]:bg-primary data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) return\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) return\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) return \"active\"\n if (currentIndex > stepIndex) return \"completed\"\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n
    \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance, tracking } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n scrollIntoStepperPanel(node, tracking)}\n {...props}\n >\n {typeof children === \"function\"\n ? children({ step: step as T, onBeforeAction })\n : children}\n \n ))}\n \n )\n}\n\nfunction scrollIntoStepperPanel(\n node: HTMLDivElement | null,\n tracking?: boolean\n) {\n if (tracking) {\n node?.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/default/ui/button\"\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n tracking?: boolean\n}\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\nconst StepContext = React.createContext | null>(null)\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const { instance } = props\n const Scoped = instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a StepperProvider.\")\n }\n return context\n}\n\ntype StepperProps = StepperConfig &\n Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: { methods: Stepperize.Stepper }) => React.ReactNode)\n }\n\nconst Stepper = ({\n children,\n variant = \"horizontal\",\n labelOrientation = \"horizontal\",\n tracking = false,\n instance,\n ...props\n}: StepperProps) => (\n \n {children}\n \n)\n\nconst StepperContainer = ({\n children,\n className,\n ...props\n}: Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: { methods: Stepperize.Stepper }) => React.ReactNode)\n}) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n return (\n
    \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children: React.ReactNode\n}) => {\n const { variant } = useStepper()\n\n return (\n \n
      {children}
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n state,\n disabled,\n}: {\n isLast: boolean\n state: string\n disabled?: boolean\n} & VariantProps) => {\n if (isLast) {\n return null\n }\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted\",\n \"data-[state=completed]:bg-primary data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) {\n return\n }\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) {\n return\n }\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) {\n return \"active\"\n }\n if (currentIndex > stepIndex) {\n return \"completed\"\n }\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"div\"> & {\n asChild?: boolean\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { tracking } = useStepper()\n\n return (\n scrollIntoStepperPanel(node, tracking)}\n {...props}\n >\n {children}\n \n )\n}\n\nfunction scrollIntoStepperPanel(\n node: HTMLDivElement | null,\n tracking?: boolean\n) {\n if (tracking) {\n node?.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n}\n\nconst StepperControls = ({\n children,\n asChild,\n ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"div\"\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n defineStepper,\n Stepper,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-demo.json b/apps/www/public/r/styles/new-york/stepper-demo.json index 8732ce9be87..28bd7d4a481 100644 --- a/apps/www/public/r/styles/new-york/stepper-demo.json +++ b/apps/www/public/r/styles/new-york/stepper-demo.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-demo.tsx", - "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperDemo() {\n return (\n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-description.json b/apps/www/public/r/styles/new-york/stepper-description.json index 5b3d1ba2d5f..ce1a4098be2 100644 --- a/apps/www/public/r/styles/new-york/stepper-description.json +++ b/apps/www/public/r/styles/new-york/stepper-description.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-description.tsx", - "content": "import {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n description: \"This is the first step\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n description: \"This is the second step\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n description: \"This is the third step\",\n }\n)\n\nexport default function StepperDemo() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {step.description}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n description: \"This is the first step\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n description: \"This is the second step\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n description: \"This is the third step\",\n }\n)\n\nexport default function StepperDemo() {\n return (\n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {step.description}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-form.json b/apps/www/public/r/styles/new-york/stepper-form.json index f177415f0e8..c9529b817da 100644 --- a/apps/www/public/r/styles/new-york/stepper-form.json +++ b/apps/www/public/r/styles/new-york/stepper-form.json @@ -10,7 +10,7 @@ "files": [ { "path": "examples/stepper-form.tsx", - "content": "import { zodResolver } from \"@hookform/resolvers/zod\"\nimport { useForm, useFormContext } from \"react-hook-form\"\nimport { z } from \"zod\"\n\nimport { Form } from \"@/registry/new-york/ui/form\"\nimport { Input } from \"@/registry/new-york/ui/input\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst shippingSchema = z.object({\n address: z.string().min(1, \"Address is required\"),\n city: z.string().min(1, \"City is required\"),\n postalCode: z.string().min(5, \"Postal code is required\"),\n})\n\nconst paymentSchema = z.object({\n cardNumber: z.string().min(16, \"Card number is required\"),\n expirationDate: z.string().min(5, \"Expiration date is required\"),\n cvv: z.string().min(3, \"CVV is required\"),\n})\n\ntype ShippingFormValues = z.infer\ntype PaymentFormValues = z.infer\n\nconst stepperInstance = defineStepper(\n {\n id: \"shipping\",\n title: \"Shipping\",\n schema: shippingSchema,\n },\n {\n id: \"payment\",\n title: \"Payment\",\n schema: paymentSchema,\n },\n {\n id: \"complete\",\n title: \"Complete\",\n schema: z.object({}),\n }\n)\n\nexport default function StepperForm() {\n return (\n \n \n \n )\n}\n\nconst FormStepperComponent = () => {\n const { steps, useStepper, utils } = stepperInstance\n const methods = useStepper()\n\n const form = useForm({\n mode: \"onTouched\",\n resolver: zodResolver(methods.current.schema),\n })\n\n const onSubmit = (values: z.infer) => {\n console.log(`Form values for step ${methods.current.id}:`, values)\n }\n\n const currentIndex = utils.getIndex(methods.current.id)\n\n return (\n
    \n \n \n {steps.map((step) => (\n {\n const valid = await form.trigger()\n if (!valid) return\n if (utils.getIndex(step.id) - currentIndex > 1) return\n methods.goTo(step.id)\n }}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n shipping: () => ,\n payment: () => ,\n complete: () => ,\n })}\n \n Previous\n {\n const valid = await form.trigger()\n if (!valid) return false\n if (utils.getIndex(nextStep.id as any) - currentIndex > 1)\n return false\n return true\n }}\n >\n Next\n \n Reset\n \n \n \n )\n}\n\nconst ShippingForm = () => {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Address\n \n \n {errors.address && (\n \n {errors.address.message}\n \n )}\n
    \n
    \n \n City\n \n \n {errors.city && (\n \n {errors.city.message}\n \n )}\n
    \n
    \n \n Postal Code\n \n \n {errors.postalCode && (\n \n {errors.postalCode.message}\n \n )}\n
    \n
    \n )\n}\n\nfunction PaymentForm() {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Card Number\n \n \n {errors.cardNumber && (\n \n {errors.cardNumber.message}\n \n )}\n
    \n
    \n \n Expiration Date\n \n \n {errors.expirationDate && (\n \n {errors.expirationDate.message}\n \n )}\n
    \n
    \n \n CVV\n \n \n {errors.cvv && (\n {errors.cvv.message}\n )}\n
    \n
    \n )\n}\n\nfunction CompleteComponent() {\n return
    Thank you! Your order is complete.
    \n}\n", + "content": "import * as React from \"react\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport { useForm, useFormContext } from \"react-hook-form\"\nimport { z } from \"zod\"\n\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport { Form } from \"@/registry/new-york/ui/form\"\nimport { Input } from \"@/registry/new-york/ui/input\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst shippingSchema = z.object({\n address: z.string().min(1, \"Address is required\"),\n city: z.string().min(1, \"City is required\"),\n postalCode: z.string().min(5, \"Postal code is required\"),\n})\n\nconst paymentSchema = z.object({\n cardNumber: z.string().min(16, \"Card number is required\"),\n expirationDate: z.string().min(5, \"Expiration date is required\"),\n cvv: z.string().min(3, \"CVV is required\"),\n})\n\ntype ShippingFormValues = z.infer\ntype PaymentFormValues = z.infer\n\nconst ShippingForm = () => {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Address\n \n \n {errors.address && (\n \n {errors.address.message}\n \n )}\n
    \n
    \n \n City\n \n \n {errors.city && (\n \n {errors.city.message}\n \n )}\n
    \n
    \n \n Postal Code\n \n \n {errors.postalCode && (\n \n {errors.postalCode.message}\n \n )}\n
    \n
    \n )\n}\n\nfunction PaymentForm() {\n const {\n register,\n formState: { errors },\n } = useFormContext()\n\n return (\n
    \n
    \n \n Card Number\n \n \n {errors.cardNumber && (\n \n {errors.cardNumber.message}\n \n )}\n
    \n
    \n \n Expiration Date\n \n \n {errors.expirationDate && (\n \n {errors.expirationDate.message}\n \n )}\n
    \n
    \n \n CVV\n \n \n {errors.cvv && (\n {errors.cvv.message}\n )}\n
    \n
    \n )\n}\n\nfunction CompleteComponent() {\n return
    Thank you! Your order is complete.
    \n}\n\nconst stepperInstance = defineStepper(\n {\n id: \"shipping\",\n title: \"Shipping\",\n schema: shippingSchema,\n Component: ShippingForm,\n },\n {\n id: \"payment\",\n title: \"Payment\",\n schema: paymentSchema,\n Component: PaymentForm,\n },\n {\n id: \"complete\",\n title: \"Complete\",\n schema: z.object({}),\n Component: CompleteComponent,\n }\n)\n\nexport default function StepperForm() {\n return (\n \n \n \n )\n}\n\nconst FormStepperComponent = () => {\n const { useStepper } = stepperInstance\n const methods = useStepper()\n\n const form = useForm({\n mode: \"onTouched\",\n resolver: zodResolver(methods.current.schema),\n })\n\n const onSubmit = (values: z.infer) => {\n console.log(`Form values for step ${methods.current.id}:`, values)\n }\n\n return (\n
    \n \n \n {methods.all.map((step) => (\n {\n const valid = await form.trigger()\n if (!valid) return\n methods.goTo(step.id)\n }}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n shipping: ({ Component }) => ,\n payment: ({ Component }) => ,\n complete: ({ Component }) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n {\n if (methods.isLast) {\n return methods.reset()\n }\n methods.beforeNext(async () => {\n const valid = await form.trigger()\n if (!valid) return false\n return true\n })\n }}\n >\n {methods.isLast ? \"Reset\" : \"Next\"}\n \n \n \n \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-icon.json b/apps/www/public/r/styles/new-york/stepper-icon.json index e9573a8f65e..d91ad347537 100644 --- a/apps/www/public/r/styles/new-york/stepper-icon.json +++ b/apps/www/public/r/styles/new-york/stepper-icon.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-icon.tsx", - "content": "import { HomeIcon, SettingsIcon, UserIcon } from \"lucide-react\"\n\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n icon: ,\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n icon: ,\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n icon: ,\n }\n)\n\nexport default function StepperIcon() {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n icon={step.icon}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\nimport { HomeIcon, SettingsIcon, UserIcon } from \"lucide-react\"\n\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n icon: ,\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n icon: ,\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n icon: ,\n }\n)\n\nexport default function StepperDemo() {\n return (\n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n icon={step.icon}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-label-orientation.json b/apps/www/public/r/styles/new-york/stepper-label-orientation.json index c6e985863df..d5005d0045b 100644 --- a/apps/www/public/r/styles/new-york/stepper-label-orientation.json +++ b/apps/www/public/r/styles/new-york/stepper-label-orientation.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-label-orientation.tsx", - "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype LabelOrientation = \"horizontal\" | \"vertical\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [labelOrientation, setLabelOrientation] =\n React.useState(\"horizontal\")\n return (\n
    \n \n setLabelOrientation(value as LabelOrientation)\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n
    \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype LabelOrientation = \"horizontal\" | \"vertical\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const [labelOrientation, setLabelOrientation] =\n React.useState(\"horizontal\")\n return (\n
    \n \n setLabelOrientation(value as LabelOrientation)\n }\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n \n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n
    \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-responsive-variant.json b/apps/www/public/r/styles/new-york/stepper-responsive-variant.json index 0b858184023..0800e76a9e6 100644 --- a/apps/www/public/r/styles/new-york/stepper-responsive-variant.json +++ b/apps/www/public/r/styles/new-york/stepper-responsive-variant.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-responsive-variant.tsx", - "content": "import { useMediaQuery } from \"@/hooks/use-media-query\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperResponsiveVariant() {\n const steps = stepperInstance.steps\n const isMobile = useMediaQuery(\"(max-width: 768px)\")\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {isMobile && (\n \n {({ step }) => (\n

    \n Content for {step.id}\n

    \n )}\n \n )}\n \n ))}\n
    \n {!isMobile &&\n steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { useMediaQuery } from \"@/hooks/use-media-query\"\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperResponsiveVariant() {\n const isMobile = useMediaQuery(\"(max-width: 768px)\")\n return (\n \n {({ methods }) => (\n <>\n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {isMobile &&\n methods.when(step.id, (step) => (\n \n

    \n Content for {step.id}\n

    \n
    \n ))}\n \n ))}\n
    \n {!isMobile &&\n methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-tracking.json b/apps/www/public/r/styles/new-york/stepper-tracking.json index 1b8733dc0ed..bb990294c11 100644 --- a/apps/www/public/r/styles/new-york/stepper-tracking.json +++ b/apps/www/public/r/styles/new-york/stepper-tracking.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-tracking.tsx", - "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n },\n {\n id: \"step-4\",\n title: \"Step 4\",\n },\n {\n id: \"step-5\",\n title: \"Step 5\",\n },\n {\n id: \"step-6\",\n title: \"Step 6\",\n }\n)\n\nexport default function StepperVerticalFollow() {\n const steps = stepperInstance.steps\n\n const [tracking, setTracking] = React.useState(false)\n return (\n
    \n setTracking(value === \"true\")}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n
    \n

    \n Content for {step.id}\n

    \n
    \n \n Previous\n Next\n Reset\n \n
    \n \n ))}\n
    \n \n )}\n \n
    \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n },\n {\n id: \"step-4\",\n title: \"Step 4\",\n },\n {\n id: \"step-5\",\n title: \"Step 5\",\n },\n {\n id: \"step-6\",\n title: \"Step 6\",\n }\n)\n\nexport default function StepperVerticalFollow() {\n const [tracking, setTracking] = React.useState(false)\n return (\n
    \n setTracking(value === \"true\")}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n \n \n {({ methods }) => (\n <>\n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {methods.when(step.id, () => (\n \n
    \n

    \n Content for {step.id}\n

    \n
    \n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n {methods.isLast ? \"Reset\" : \"Next\"}\n \n \n
    \n ))}\n \n ))}\n
    \n \n )}\n \n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper-variants.json b/apps/www/public/r/styles/new-york/stepper-variants.json index fa53f00b7ee..b158ed315d5 100644 --- a/apps/www/public/r/styles/new-york/stepper-variants.json +++ b/apps/www/public/r/styles/new-york/stepper-variants.json @@ -9,7 +9,7 @@ "files": [ { "path": "examples/stepper-variants.tsx", - "content": "import * as React from \"react\"\n\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const steps = stepperInstance.steps\n\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n setVariant(value as Variant)}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n\nconst VerticalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n \n ))}\n
    \n \n Previous\n Next\n Reset\n \n \n )}\n \n )\n}\n\nconst CircleStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n <>\n \n \n {methods.current.title}\n \n \n {steps.map((step) => (\n \n {({ step }) => (\n

    Content for {step.id}

    \n )}\n \n ))}\n \n Previous\n Next\n Reset\n \n \n )}\n
    \n )\n}\n", + "content": "import * as React from \"react\"\n\nimport { Button } from \"@/registry/new-york/ui/button\"\nimport { Label } from \"@/registry/new-york/ui/label\"\nimport { RadioGroup, RadioGroupItem } from \"@/registry/new-york/ui/radio-group\"\nimport {\n Stepper,\n StepperControls,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n} from \"@/registry/new-york/ui/stepper\"\n\ntype Variant = \"horizontal\" | \"vertical\" | \"circle\"\n\nconst stepperInstance = defineStepper(\n {\n id: \"step-1\",\n title: \"Step 1\",\n },\n {\n id: \"step-2\",\n title: \"Step 2\",\n },\n {\n id: \"step-3\",\n title: \"Step 3\",\n }\n)\n\nexport default function StepperVariants() {\n const [variant, setVariant] = React.useState(\"horizontal\")\n return (\n
    \n setVariant(value as Variant)}\n >\n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n \n {variant === \"horizontal\" && }\n {variant === \"vertical\" && }\n {variant === \"circle\" && }\n
    \n )\n}\n\nconst HorizontalStepper = () => {\n const steps = stepperInstance.steps\n return (\n \n {({ methods }) => (\n \n \n {steps.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n \n ))}\n \n {methods.switch({\n \"step-1\": (step) => ,\n \"step-2\": (step) => ,\n \"step-3\": (step) => ,\n })}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst Content = ({ id }: { id: string }) => {\n return (\n \n

    Content for {id}

    \n
    \n )\n}\n\nconst VerticalStepper = () => {\n return (\n \n {({ methods }) => (\n <>\n \n {methods.all.map((step) => (\n methods.goTo(step.id)}\n >\n {step.title}\n {methods.when(step.id, () => (\n \n

    Content for {step.id}

    \n
    \n ))}\n \n ))}\n
    \n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n \n )\n}\n\nconst CircleStepper = () => {\n return (\n \n {({ methods }) => (\n <>\n \n \n {methods.current.title}\n \n \n {methods.when(methods.current.id, () => (\n \n

    \n Content for {methods.current.id}\n

    \n
    \n ))}\n \n {!methods.isLast && (\n \n Previous\n \n )}\n \n \n \n )}\n
    \n )\n}\n", "type": "registry:example", "target": "" } diff --git a/apps/www/public/r/styles/new-york/stepper.json b/apps/www/public/r/styles/new-york/stepper.json index a1ba04d15ee..df1a9f4d3a5 100644 --- a/apps/www/public/r/styles/new-york/stepper.json +++ b/apps/www/public/r/styles/new-york/stepper.json @@ -14,7 +14,7 @@ "files": [ { "path": "ui/stepper.tsx", - "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { VariantProps, cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n tracking?: boolean\n}\n\nconst StepContext = React.createContext>({\n instance: {} as ReturnType>,\n variant: \"horizontal\",\n})\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const Scope = props.instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a Stepper\")\n }\n return context\n}\n\nfunction Stepper({\n children,\n variant = \"horizontal\",\n className,\n labelOrientation = \"horizontal\",\n tracking = false,\n ...props\n}: StepperConfig &\n Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: { methods: Stepperize.Stepper }) => React.ReactNode)\n }) {\n const { instance } = props\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
    \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children: React.ReactNode\n}) => {\n const { variant, instance } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n return (\n \n
      {children}
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const id = React.useId()\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper() as Stepperize.Stepper\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n state,\n disabled,\n}: {\n isLast: boolean\n state: string\n disabled?: boolean\n} & VariantProps) => {\n if (isLast) return null\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted\",\n \"data-[state=completed]:bg-primary data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) return\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) return\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) return \"active\"\n if (currentIndex > stepIndex) return \"completed\"\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n when,\n asChild,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n when: T\n children:\n | React.ReactNode\n | ((props: {\n step: T\n onBeforeAction: (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => void\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance, tracking } = useStepper()\n\n const methods = instance.useStepper()\n\n if (instance.utils.getIndex(when.id) === -1) {\n throw new Error(`Step ${when.id} does not exist in the stepper instance`)\n }\n\n const onBeforeAction = React.useCallback(\n async (\n action: StepAction,\n callback: (params: {\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n ) => {\n const prevStep = methods.current\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(prevStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(prevStep.id)\n : instance.utils.getFirst()\n\n const shouldProceed = await callback({ prevStep, nextStep })\n if (shouldProceed) {\n if (action === \"next\") methods.next()\n if (action === \"prev\") methods.prev()\n if (action === \"reset\") methods.reset()\n }\n },\n [methods, instance.utils]\n )\n\n return (\n <>\n {methods.when(when.id, (step) => (\n scrollIntoStepperPanel(node, tracking)}\n {...props}\n >\n {typeof children === \"function\"\n ? children({ step: step as T, onBeforeAction })\n : children}\n
    \n ))}\n \n )\n}\n\nfunction scrollIntoStepperPanel(\n node: HTMLDivElement | null,\n tracking?: boolean\n) {\n if (tracking) {\n node?.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n}\n\nconst StepperControls = ({\n children,\n asChild,\n className,\n ...props\n}: Omit, \"children\"> & {\n asChild?: boolean\n children:\n | React.ReactNode\n | ((props: {\n methods: Stepperize.Stepper\n }) => React.ReactNode)\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { instance } = useStepper()\n\n const methods = instance.useStepper()\n\n return (\n \n {typeof children === \"function\" ? children({ methods }) : children}\n \n )\n}\n\ntype StepAction = \"next\" | \"prev\" | \"reset\"\n\ntype StepperActionProps = {\n action: StepAction\n children: React.ReactNode\n asChild?: boolean\n onBeforeAction?: ({\n event,\n prevStep,\n nextStep,\n }: {\n event: React.MouseEvent\n prevStep: Stepperize.Step\n nextStep: Stepperize.Step\n }) => Promise | boolean\n className?: string\n}\n\nconst StepperAction = ({\n action,\n children,\n asChild = false,\n onBeforeAction,\n className,\n disabled,\n ...props\n}: React.ComponentProps<\"button\"> & StepperActionProps) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isDisabled = (action: StepAction) =>\n action === \"prev\" && methods.isFirst\n\n const actionMap = React.useMemo(\n () => ({\n next: methods.next,\n prev: methods.prev,\n reset: methods.reset,\n }),\n [methods]\n )\n\n const handleClick = React.useCallback(\n async (event: React.MouseEvent) => {\n if (onBeforeAction) {\n const nextStep =\n action === \"next\"\n ? instance.utils.getNext(currentStep.id)\n : action === \"prev\"\n ? instance.utils.getPrev(currentStep.id)\n : instance.utils.getFirst()\n const shouldProceed = await onBeforeAction({\n event,\n prevStep: currentStep,\n nextStep,\n })\n if (!shouldProceed) {\n return\n }\n }\n\n actionMap[action]?.()\n },\n [onBeforeAction, actionMap, action, instance.utils, currentStep]\n )\n\n const Comp = asChild ? Slot : Button\n\n if (\n (methods.isLast && (action === \"next\" || action === \"prev\")) ||\n (!methods.isLast && action === \"reset\")\n ) {\n return null\n }\n\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n Stepper,\n StepperAction,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n defineStepper,\n}\n", + "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport * as Stepperize from \"@stepperize/react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york/ui/button\"\n\ntype StepperVariant = \"horizontal\" | \"vertical\" | \"circle\"\ntype StepperLabelOrientation = \"horizontal\" | \"vertical\"\n\ntype StepperConfig = {\n instance: ReturnType>\n variant?: StepperVariant\n labelOrientation?: StepperLabelOrientation\n tracking?: boolean\n}\n\ntype StepperProviderProps = StepperConfig & {\n children: React.ReactNode\n}\n\nconst StepContext = React.createContext | null>(null)\n\nconst StepperProvider = ({\n children,\n ...props\n}: StepperProviderProps) => {\n const { instance } = props\n const Scoped = instance.Scoped\n return (\n \n {children}\n \n )\n}\n\nconst useStepper = (): StepperConfig => {\n const context = React.useContext(StepContext)\n if (!context) {\n throw new Error(\"useStepper must be used within a StepperProvider.\")\n }\n return context\n}\n\ntype StepperProps = StepperConfig &\n Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: { methods: Stepperize.Stepper }) => React.ReactNode)\n }\n\nconst Stepper = ({\n children,\n variant = \"horizontal\",\n labelOrientation = \"horizontal\",\n tracking = false,\n instance,\n ...props\n}: StepperProps) => (\n \n {children}\n \n)\n\nconst StepperContainer = ({\n children,\n className,\n ...props\n}: Omit, \"children\"> & {\n children:\n | React.ReactNode\n | ((props: { methods: Stepperize.Stepper }) => React.ReactNode)\n}) => {\n const { instance } = useStepper()\n const methods = instance.useStepper()\n\n return (\n
    \n {typeof children === \"function\" ? children({ methods }) : children}\n
    \n )\n}\n\nconst StepperNavigation = ({\n children,\n className,\n \"aria-label\": ariaLabel = \"Stepper Navigation\",\n ...props\n}: Omit, \"children\"> & {\n children: React.ReactNode\n}) => {\n const { variant } = useStepper()\n\n return (\n \n
      {children}
    \n \n )\n}\n\nconst listVariants = cva(\"stepper-navigation-list flex gap-2\", {\n variants: {\n variant: {\n horizontal: \"flex-row items-center justify-between\",\n vertical: \"flex-col\",\n circle: \"flex-row items-center justify-between\",\n },\n },\n})\n\nconst StepperStep = ({\n children,\n className,\n of,\n icon,\n ...props\n}: React.ComponentProps<\"button\"> & { of: T; icon?: Icon }) => {\n const { instance, variant, labelOrientation } = useStepper()\n\n const methods = instance.useStepper()\n\n const currentStep = methods.current\n\n const isLast = instance.utils.getLast().id === of.id\n const stepIndex = instance.utils.getIndex(of.id)\n const currentIndex = instance.utils.getIndex(currentStep?.id ?? \"\")\n const isActive = currentStep?.id === of.id\n\n const dataState = getStepState(currentIndex, stepIndex)\n const childMap = useStepChildren(children)\n\n const title = childMap.get(\"title\")\n const description = childMap.get(\"description\")\n const panel = childMap.get(\"panel\")\n\n if (variant === \"circle\") {\n return (\n \n \n
    \n {title}\n {description}\n
    \n \n )\n }\n\n return (\n <>\n \n \n onStepKeyDown(\n e,\n instance.utils.getNext(of.id),\n instance.utils.getPrev(of.id)\n )\n }\n {...props}\n >\n {icon ?? stepIndex + 1}\n \n {variant === \"horizontal\" && labelOrientation === \"vertical\" && (\n \n )}\n
    \n {title}\n {description}\n
    \n \n\n {variant === \"horizontal\" && labelOrientation === \"horizontal\" && (\n \n )}\n\n {variant === \"vertical\" && (\n
    \n {!isLast && (\n
    \n \n
    \n )}\n
    {panel}
    \n
    \n )}\n \n )\n}\n\nconst StepperSeparator = ({\n orientation,\n isLast,\n labelOrientation,\n state,\n disabled,\n}: {\n isLast: boolean\n state: string\n disabled?: boolean\n} & VariantProps) => {\n if (isLast) {\n return null\n }\n return (\n \n )\n}\n\nconst classForSeparator = cva(\n [\n \"bg-muted\",\n \"data-[state=completed]:bg-primary data-[disabled]:opacity-50\",\n \"transition-all duration-300 ease-in-out\",\n ],\n {\n variants: {\n orientation: {\n horizontal: \"h-0.5 flex-1\",\n vertical: \"h-full w-0.5\",\n },\n labelOrientation: {\n vertical:\n \"absolute left-[calc(50%+30px)] right-[calc(-50%+20px)] top-5 block shrink-0\",\n },\n },\n }\n)\n\nconst onStepKeyDown = (\n e: React.KeyboardEvent,\n nextStep: Stepperize.Step,\n prevStep: Stepperize.Step\n) => {\n const { key } = e\n const directions = {\n next: [\"ArrowRight\", \"ArrowDown\"],\n prev: [\"ArrowLeft\", \"ArrowUp\"],\n }\n\n if (directions.next.includes(key) || directions.prev.includes(key)) {\n const direction = directions.next.includes(key) ? \"next\" : \"prev\"\n const step = direction === \"next\" ? nextStep : prevStep\n\n if (!step) {\n return\n }\n\n const stepElement = document.getElementById(`step-${step.id}`)\n if (!stepElement) {\n return\n }\n\n const isActive =\n stepElement.parentElement?.getAttribute(\"data-state\") !== \"inactive\"\n if (isActive || direction === \"prev\") {\n stepElement.focus()\n }\n }\n}\n\nconst getStepState = (currentIndex: number, stepIndex: number) => {\n if (currentIndex === stepIndex) {\n return \"active\"\n }\n if (currentIndex > stepIndex) {\n return \"completed\"\n }\n return \"inactive\"\n}\n\nconst extractChildren = (children: React.ReactNode) => {\n const childrenArray = React.Children.toArray(children)\n const map = new Map()\n\n for (const child of childrenArray) {\n if (React.isValidElement(child)) {\n if (child.type === StepperTitle) {\n map.set(\"title\", child)\n } else if (child.type === StepperDescription) {\n map.set(\"description\", child)\n } else if (child.type === StepperPanel) {\n map.set(\"panel\", child)\n }\n }\n }\n\n return map\n}\n\nconst useStepChildren = (children: React.ReactNode) => {\n return React.useMemo(() => extractChildren(children), [children])\n}\n\nconst StepperTitle = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"h4\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"h4\"\n\n return (\n \n {children}\n \n )\n}\n\nconst StepperDescription = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"p\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"p\"\n\n return (\n \n {children}\n \n )\n}\n\ntype CircleStepIndicatorProps = {\n currentStep: number\n totalSteps: number\n size?: number\n strokeWidth?: number\n}\n\nconst CircleStepIndicator = ({\n currentStep,\n totalSteps,\n size = 80,\n strokeWidth = 6,\n}: CircleStepIndicatorProps) => {\n const radius = (size - strokeWidth) / 2\n const circumference = radius * 2 * Math.PI\n const fillPercentage = (currentStep / totalSteps) * 100\n const dashOffset = circumference - (circumference * fillPercentage) / 100\n return (\n \n \n Step Indicator\n \n \n \n
    \n \n {currentStep} of {totalSteps}\n \n
    \n
    \n )\n}\n\nconst StepperPanel = ({\n children,\n className,\n asChild,\n ...props\n}: React.ComponentProps<\"div\"> & {\n asChild?: boolean\n}) => {\n const Comp = asChild ? Slot : \"div\"\n const { tracking } = useStepper()\n\n return (\n scrollIntoStepperPanel(node, tracking)}\n {...props}\n >\n {children}\n \n )\n}\n\nfunction scrollIntoStepperPanel(\n node: HTMLDivElement | null,\n tracking?: boolean\n) {\n if (tracking) {\n node?.scrollIntoView({ behavior: \"smooth\", block: \"center\" })\n }\n}\n\nconst StepperControls = ({\n children,\n asChild,\n ...props\n}: React.ComponentProps<\"div\"> & { asChild?: boolean }) => {\n const Comp = asChild ? Slot : \"div\"\n return (\n \n {children}\n \n )\n}\n\nconst defineStepper: typeof Stepperize.defineStepper = Stepperize.defineStepper\n\nexport {\n defineStepper,\n Stepper,\n StepperControls,\n StepperDescription,\n StepperNavigation,\n StepperPanel,\n StepperStep,\n StepperTitle,\n}\n", "type": "registry:ui", "target": "" } diff --git a/apps/www/registry/default/examples/stepper-demo.tsx b/apps/www/registry/default/examples/stepper-demo.tsx index d77ef0677c3..50bf927e75b 100644 --- a/apps/www/registry/default/examples/stepper-demo.tsx +++ b/apps/www/registry/default/examples/stepper-demo.tsx @@ -1,6 +1,8 @@ +import * as React from "react" + +import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperAction, StepperControls, StepperNavigation, StepperPanel, @@ -25,7 +27,6 @@ const stepperInstance = defineStepper( ) export default function StepperDemo() { - const steps = stepperInstance.steps return ( {({ methods }) => ( - <> + - {steps.map((step) => ( + {methods.all.map((step) => ( ))} - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} + {methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} - Previous - Next - Reset + {!methods.isLast && ( + + )} + - +
    )}
    ) } + +const Content = ({ id }: { id: string }) => { + return ( + +

    Content for {id}

    +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-description.tsx b/apps/www/registry/default/examples/stepper-description.tsx index 5bb54592c7b..f28f20a384b 100644 --- a/apps/www/registry/default/examples/stepper-description.tsx +++ b/apps/www/registry/default/examples/stepper-description.tsx @@ -1,6 +1,8 @@ +import * as React from "react" + +import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperAction, StepperControls, StepperDescription, StepperNavigation, @@ -29,7 +31,6 @@ const stepperInstance = defineStepper( ) export default function StepperDemo() { - const steps = stepperInstance.steps return ( {({ methods }) => ( - <> + - {steps.map((step) => ( + {methods.all.map((step) => ( ))} - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} + {methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} - Previous - Next - Reset + {!methods.isLast && ( + + )} + - +
    )}
    ) } + +const Content = ({ id }: { id: string }) => { + return ( + +

    Content for {id}

    +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-form.tsx b/apps/www/registry/default/examples/stepper-form.tsx index a5c3184d98a..dbbbdb069d7 100644 --- a/apps/www/registry/default/examples/stepper-form.tsx +++ b/apps/www/registry/default/examples/stepper-form.tsx @@ -1,12 +1,13 @@ +import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { useForm, useFormContext } from "react-hook-form" import { z } from "zod" +import { Button } from "@/registry/default/ui/button" import { Form } from "@/registry/default/ui/form" import { Input } from "@/registry/default/ui/input" import { Stepper, - StepperAction, StepperControls, StepperNavigation, StepperStep, @@ -29,93 +30,6 @@ const paymentSchema = z.object({ type ShippingFormValues = z.infer type PaymentFormValues = z.infer -const stepperInstance = defineStepper( - { - id: "shipping", - title: "Shipping", - schema: shippingSchema, - }, - { - id: "payment", - title: "Payment", - schema: paymentSchema, - }, - { - id: "complete", - title: "Complete", - schema: z.object({}), - } -) - -export default function StepperForm() { - return ( - - - - ) -} - -const FormStepperComponent = () => { - const { steps, useStepper, utils } = stepperInstance - const methods = useStepper() - - const form = useForm({ - mode: "onTouched", - resolver: zodResolver(methods.current.schema), - }) - - const onSubmit = (values: z.infer) => { - console.log(`Form values for step ${methods.current.id}:`, values) - } - - const currentIndex = utils.getIndex(methods.current.id) - - return ( -
    - - - {steps.map((step) => ( - { - const valid = await form.trigger() - if (!valid) return - if (utils.getIndex(step.id) - currentIndex > 1) return - methods.goTo(step.id) - }} - > - {step.title} - - ))} - - {methods.switch({ - shipping: () => , - payment: () => , - complete: () => , - })} - - Previous - { - const valid = await form.trigger() - if (!valid) return false - if (utils.getIndex(nextStep.id as any) - currentIndex > 1) - return false - return true - }} - > - Next - - Reset - - - - ) -} - const ShippingForm = () => { const { register, @@ -249,3 +163,100 @@ function PaymentForm() { function CompleteComponent() { return
    Thank you! Your order is complete.
    } + +const stepperInstance = defineStepper( + { + id: "shipping", + title: "Shipping", + schema: shippingSchema, + Component: ShippingForm, + }, + { + id: "payment", + title: "Payment", + schema: paymentSchema, + Component: PaymentForm, + }, + { + id: "complete", + title: "Complete", + schema: z.object({}), + Component: CompleteComponent, + } +) + +export default function StepperForm() { + return ( + + + + ) +} + +const FormStepperComponent = () => { + const { useStepper } = stepperInstance + const methods = useStepper() + + const form = useForm({ + mode: "onTouched", + resolver: zodResolver(methods.current.schema), + }) + + const onSubmit = (values: z.infer) => { + console.log(`Form values for step ${methods.current.id}:`, values) + } + + return ( +
    + + + {methods.all.map((step) => ( + { + const valid = await form.trigger() + if (!valid) return + methods.goTo(step.id) + }} + > + {step.title} + + ))} + + {methods.switch({ + shipping: ({ Component }) => , + payment: ({ Component }) => , + complete: ({ Component }) => , + })} + + {!methods.isLast && ( + + )} + + + + + ) +} diff --git a/apps/www/registry/default/examples/stepper-icon.tsx b/apps/www/registry/default/examples/stepper-icon.tsx index 45caf09fb9e..a6039f007ee 100644 --- a/apps/www/registry/default/examples/stepper-icon.tsx +++ b/apps/www/registry/default/examples/stepper-icon.tsx @@ -1,8 +1,9 @@ +import * as React from "react" import { HomeIcon, SettingsIcon, UserIcon } from "lucide-react" +import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperAction, StepperControls, StepperNavigation, StepperPanel, @@ -29,8 +30,7 @@ const stepperInstance = defineStepper( } ) -export default function StepperIcon() { - const steps = stepperInstance.steps +export default function StepperDemo() { return ( {({ methods }) => ( - <> + - {steps.map((step) => ( + {methods.all.map((step) => ( ))} - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} + {methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} - Previous - Next - Reset + {!methods.isLast && ( + + )} + - +
    )}
    ) } + +const Content = ({ id }: { id: string }) => { + return ( + +

    Content for {id}

    +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-label-orientation.tsx b/apps/www/registry/default/examples/stepper-label-orientation.tsx index 136a02d15d4..f50027a9695 100644 --- a/apps/www/registry/default/examples/stepper-label-orientation.tsx +++ b/apps/www/registry/default/examples/stepper-label-orientation.tsx @@ -1,10 +1,10 @@ import * as React from "react" +import { Button } from "@/registry/default/ui/button" import { Label } from "@/registry/default/ui/label" import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" import { Stepper, - StepperAction, StepperControls, StepperNavigation, StepperPanel, @@ -31,8 +31,6 @@ const stepperInstance = defineStepper( ) export default function StepperVariants() { - const steps = stepperInstance.steps - const [labelOrientation, setLabelOrientation] = React.useState("horizontal") return ( @@ -59,9 +57,9 @@ export default function StepperVariants() { labelOrientation={labelOrientation} > {({ methods }) => ( - <> + - {steps.map((step) => ( + {methods.all.map((step) => ( ))} - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} + {methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} - Previous - Next - Reset + {!methods.isLast && ( + + )} + - +
    )}
    ) } + +const Content = ({ id }: { id: string }) => { + return ( + +

    Content for {id}

    +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-responsive-variant.tsx b/apps/www/registry/default/examples/stepper-responsive-variant.tsx index 85fb106db93..5f2cc917961 100644 --- a/apps/www/registry/default/examples/stepper-responsive-variant.tsx +++ b/apps/www/registry/default/examples/stepper-responsive-variant.tsx @@ -1,7 +1,9 @@ +import * as React from "react" + import { useMediaQuery } from "@/hooks/use-media-query" +import { Button } from "@/registry/default/ui/button" import { Stepper, - StepperAction, StepperControls, StepperNavigation, StepperPanel, @@ -26,7 +28,6 @@ const stepperInstance = defineStepper( ) export default function StepperResponsiveVariant() { - const steps = stepperInstance.steps const isMobile = useMediaQuery("(max-width: 768px)") return ( ( <> - {steps.map((step) => ( + {methods.all.map((step) => ( methods.goTo(step.id)} > {step.title} - {isMobile && ( - - {({ step }) => ( + {isMobile && + methods.when(step.id, (step) => ( +

    Content for {step.id}

    - )} -
    - )} +
    + ))}
    ))}
    {!isMobile && - steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} + methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} - Previous - Next - Reset + {!methods.isLast && ( + + )} + )}
    ) } + +const Content = ({ id }: { id: string }) => { + return ( + +

    Content for {id}

    +
    + ) +} diff --git a/apps/www/registry/default/examples/stepper-tracking.tsx b/apps/www/registry/default/examples/stepper-tracking.tsx index 9f8a550e06f..d745fdf4d34 100644 --- a/apps/www/registry/default/examples/stepper-tracking.tsx +++ b/apps/www/registry/default/examples/stepper-tracking.tsx @@ -1,10 +1,10 @@ import * as React from "react" +import { Button } from "@/registry/default/ui/button" import { Label } from "@/registry/default/ui/label" import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" import { Stepper, - StepperAction, StepperControls, StepperNavigation, StepperPanel, @@ -41,8 +41,6 @@ const stepperInstance = defineStepper( ) export default function StepperVerticalFollow() { - const steps = stepperInstance.steps - const [tracking, setTracking] = React.useState(false) return (
    @@ -68,25 +66,40 @@ export default function StepperVerticalFollow() { {({ methods }) => ( <> - {steps.map((step) => ( + {methods.all.map((step) => ( methods.goTo(step.id)} > {step.title} - -
    -

    - Content for {step.id} -

    -
    - - Previous - Next - Reset - -
    + {methods.when(step.id, () => ( + +
    +

    + Content for {step.id} +

    +
    + + {!methods.isLast && ( + + )} + + +
    + ))}
    ))}
    diff --git a/apps/www/registry/default/examples/stepper-variants.tsx b/apps/www/registry/default/examples/stepper-variants.tsx index f8aaef2a02d..06c12a65141 100644 --- a/apps/www/registry/default/examples/stepper-variants.tsx +++ b/apps/www/registry/default/examples/stepper-variants.tsx @@ -1,10 +1,10 @@ import * as React from "react" +import { Button } from "@/registry/default/ui/button" import { Label } from "@/registry/default/ui/label" import { RadioGroup, RadioGroupItem } from "@/registry/default/ui/radio-group" import { Stepper, - StepperAction, StepperControls, StepperNavigation, StepperPanel, @@ -31,8 +31,6 @@ const stepperInstance = defineStepper( ) export default function StepperVariants() { - const steps = stepperInstance.steps - const [variant, setVariant] = React.useState("horizontal") return (
    @@ -69,7 +67,7 @@ const HorizontalStepper = () => { variant="horizontal" > {({ methods }) => ( - <> + {steps.map((step) => ( { ))} - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} + {methods.switch({ + "step-1": (step) => , + "step-2": (step) => , + "step-3": (step) => , + })} - Previous - Next - Reset + {!methods.isLast && ( + + )} + - +
    )} ) } +const Content = ({ id }: { id: string }) => { + return ( + +

    Content for {id}

    +
    + ) +} + const VerticalStepper = () => { - const steps = stepperInstance.steps return ( { {({ methods }) => ( <> - {steps.map((step) => ( + {methods.all.map((step) => ( methods.goTo(step.id)} > {step.title} - - {({ step }) => ( + {methods.when(step.id, () => ( +

    Content for {step.id}

    - )} -
    +
    + ))}
    ))}
    - Previous - Next - Reset + {!methods.isLast && ( + + )} + )} @@ -145,7 +158,6 @@ const VerticalStepper = () => { } const CircleStepper = () => { - const steps = stepperInstance.steps return ( {({ methods }) => ( @@ -155,21 +167,26 @@ const CircleStepper = () => { {methods.current.title} - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} + {methods.when(methods.current.id, () => ( + +

    + Content for {methods.current.id} +

    ))} - Previous - Next - Reset + {!methods.isLast && ( + + )} + )} diff --git a/apps/www/registry/default/examples/stepper-with-description.tsx b/apps/www/registry/default/examples/stepper-with-description.tsx deleted file mode 100644 index 5b50b9d014a..00000000000 --- a/apps/www/registry/default/examples/stepper-with-description.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { - Stepper, - StepperAction, - StepperControls, - StepperDescription, - StepperNavigation, - StepperPanel, - StepperStep, - StepperTitle, - defineStepper, -} from "@/registry/default/ui/stepper" - -const stepperInstance = defineStepper( - { - id: "step-1", - title: "Step 1", - description: "This is the first step", - }, - { - id: "step-2", - title: "Step 2", - description: "This is the second step", - }, - { - id: "step-3", - title: "Step 3", - description: "This is the third step", - } -) - -export default function StepperWithDescription() { - const steps = stepperInstance.steps - return ( - - {({ methods }) => ( - <> - - {steps.map((step) => ( - methods.goTo(step.id)} - > - {step.title} - {step.description} - - ))} - - {steps.map((step) => ( - - {({ step }) => ( -

    Content for {step.id}

    - )} -
    - ))} - - Previous - Next - Reset - - - )} -
    - ) -} diff --git a/apps/www/registry/default/ui/stepper.tsx b/apps/www/registry/default/ui/stepper.tsx index 77ebbc599d7..053155850ac 100644 --- a/apps/www/registry/default/ui/stepper.tsx +++ b/apps/www/registry/default/ui/stepper.tsx @@ -3,15 +3,11 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import * as Stepperize from "@stepperize/react" -import { VariantProps, cva } from "class-variance-authority" +import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" import { Button } from "@/registry/default/ui/button" -type StepperProviderProps = StepperConfig & { - children: React.ReactNode -} - type StepperVariant = "horizontal" | "vertical" | "circle" type StepperLabelOrientation = "horizontal" | "vertical" @@ -22,59 +18,74 @@ type StepperConfig = { tracking?: boolean } -const StepContext = React.createContext>({ - instance: {} as ReturnType>, - variant: "horizontal", -}) +type StepperProviderProps = StepperConfig & { + children: React.ReactNode +} + +const StepContext = React.createContext | null>(null) const StepperProvider = ({ children, ...props }: StepperProviderProps) => { - const Scope = props.instance.Scoped + const { instance } = props + const Scoped = instance.Scoped return ( - + {children} - + ) } const useStepper = (): StepperConfig => { const context = React.useContext(StepContext) if (!context) { - throw new Error("useStepper must be used within a Stepper") + throw new Error("useStepper must be used within a StepperProvider.") } return context } -function Stepper({ +type StepperProps = StepperConfig & + Omit, "children"> & { + children: + | React.ReactNode + | ((props: { methods: Stepperize.Stepper }) => React.ReactNode) + } + +const Stepper = ({ children, variant = "horizontal", - className, labelOrientation = "horizontal", tracking = false, + instance, ...props -}: StepperConfig & - Omit, "children"> & { - children: - | React.ReactNode - | ((props: { methods: Stepperize.Stepper }) => React.ReactNode) - }) { - const { instance } = props +}: StepperProps) => ( + + {children} + +) - const methods = instance.useStepper() as Stepperize.Stepper +const StepperContainer = ({ + children, + className, + ...props +}: Omit, "children"> & { + children: + | React.ReactNode + | ((props: { methods: Stepperize.Stepper }) => React.ReactNode) +}) => { + const { instance } = useStepper() + const methods = instance.useStepper() return ( - -
    - {typeof children === "function" ? children({ methods }) : children} -
    -
    +
    + {typeof children === "function" ? children({ methods }) : children} +
    ) } @@ -86,9 +97,7 @@ const StepperNavigation = ({ }: Omit, "children"> & { children: React.ReactNode }) => { - const { variant, instance } = useStepper() - - const methods = instance.useStepper() as Stepperize.Stepper + const { variant } = useStepper() return (